Mathématiques avec Python et Ruby/Version imprimable2

Un livre de Wikilivres.

Ceci est la version imprimable de Mathématiques avec Python et Ruby.
  • Si vous imprimez cette page, choisissez « Aperçu avant impression » dans votre navigateur, ou cliquez sur le lien Version imprimable dans la boîte à outils, vous verrez cette page sans ce message, ni éléments de navigation sur la gauche ou en haut.
  • Cliquez sur Rafraîchir cette page pour obtenir la dernière version du wikilivre.
  • Pour plus d'informations sur les version imprimables, y compris la manière d'obtenir une version PDF, vous pouvez lire l'article Versions imprimables.


Mathématiques avec Python et Ruby

Une version à jour et éditable de ce livre est disponible sur Wikilivres,
une bibliothèque de livres pédagogiques, à l'URL :
https://fr.wikibooks.org/wiki/Math%C3%A9matiques_avec_Python_et_Ruby

Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la Licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans Texte de dernière page de couverture. Une copie de cette licence est incluse dans l'annexe nommée « Licence de documentation libre GNU ».

Nombres en Ruby

Le langage Ruby est faiblement typé, ce qui veut dire que c'est au moment de la première affectation d'une variable (son instanciation) que Ruby devine le type de cette variable. Ruby possède 5 catégories de nombres:

  1. Les nombres entiers;
  2. Les fractions (quotients d'entiers par des entiers);
  3. Les réels (en réalité des nombres décimaux);
  4. Les grands nombres (Big_Num);
  5. Les nombres complexes.


Nombres entiers en Ruby

La particularité des nombres entiers, c'est que chacun possède un successeur et un prédécesseur. Et bien Ruby sait les calculer (certes ce n'est pas très difficile, il suffit d'additionner ou soustraire 1 à un nombre entier pour avoir le suivant ou le précédent).

Obtention d'un nombre entier[modifier | modifier le wikicode]

Avec une chaîne de caractères[modifier | modifier le wikicode]

Si on entre le script suivant:

a=7
puts(a)

on a exactement le même effet que si on entre

a="7"
puts(a)

du moins en apparence. Parce que si on essaye d'additionner 2, avec

a=7
puts(a+2)

on a 9, alors qu'avec

a="7"
puts(a+2)

on a un message d'erreur: On ne peut pas additionner un nombre et une chaîne de caractères !

Pour convertir une chaîne de caractères en entier, on utilise la méthode to_i de celle-ci. Ainsi

a="7"
b=a.to_i
puts(b+2)

donne bien 9.


Un autre moyen d'obtenir un entier (naturel) avec une chaîne de caractères, c'est de compter le nombre de lettres de celle-ci. Ce qui se fait avec sa propriété length:

t="abracadabrantesque"
n=t.length
puts(n)

Avec un réel[modifier | modifier le wikicode]

La méthode to_i permet aussi de convertir un réel en entier. Ce qui est parfois nécessaire parce que pour Ruby, n'est pas un entier:

a=Math.sqrt(100)
puts(a.integer?)

Affiche false parce que pour Ruby, le nombre calculé est 10.0 (considéré comme réel et non comme entier) et sa méthode to_i change son type, en le transformant en un entier:

a=Math.sqrt(100).to_i
puts(a.integer?)

affiche bien true.

a=3.9999999
b=a.to_i
puts(b)

n'a peut-être pas l'effet escompté, puisque a a été choisi proche de 4, et qu'on obtient 3. C'est que la conversion en entier se fait par une troncature et pas par un arrondi. En fait to_i a le même effet que floor:

a=3.9999999
b=a.floor
puts(b)

Si on veut arrondir au-dessus, on utilise la méthode ceil d'un réel:

a=3.9999999
b=a.ceil
puts(b)


mais là on tombe dans le problème inverse:

a=3.0000001
b=a.ceil
puts(b)

donne aussi 4 !

Pour arrondir au mieux, on utilise la méthode round:

a=3.9999999
b=a.round
puts(b)

Avec un entier[modifier | modifier le wikicode]

Pour avoir le successeur d'un entier, on utilise la méthode succ:

puts(7.succ)

nous apprend que 7+1=8 (on s'en doutait un peu...), alors que

puts(7.pred)

montre que 7-1=6. Mais contrairement au premier des axiomes de Peano, 0 possède un prédécesseur (-1) parce que pour Ruby, les entiers sont relatifs et pas seulement naturels.

Pour avoir l'opposé d'un entier, on le précède d'un signe "moins". Ainsi,

a=-5
puts(-a)

Donne 5, car l'opposé de -5 est 5.

tests sur les entiers[modifier | modifier le wikicode]

Pour savoir si 2 est entier (on ne sait jamais), on peut le vérifier par

puts(2.integer?)

Ce test a été utilisé ci-dessus pour vérifier que 10 est entier, et on a eu raison de se méfier !

On peut aussi vérifier si un entier est premier, avec mathn:

require 'mathn'
a=2**32+1
puts(a.prime?)

nous apprend que 4 294 967 297 n'est pas premier, contrairement à ce qu'avait conjecturé Fermat.

Opérations[modifier | modifier le wikicode]

Addition, soustraction et multiplication[modifier | modifier le wikicode]

Dans Ruby, les opérations arithmétiques sont notées +, - et * sans grande surprise. Ces opérations peuvent porter sur des entiers négatifs:

a=5
b=-8
puts(a+b)
puts(a-b)
puts(a*b)

Division[modifier | modifier le wikicode]

Par défaut, la division des entiers est la division euclidienne. Son quotient est donc un entier (et n'est pas le quotient exact)

Quotient[modifier | modifier le wikicode]

le script suivant:

num=3
den=2
q=num/den
puts(q)

affiche 1 et pas 1,5 parce que le quotient euclidien de 3 par 2 est 1 (avec un reste de 1) et pas 1,5...

Si on veut le quotient exact, on doit remplacer l'un des entiers par un réel avec un point décimal. Pour avoir 1,5, on peut essayer l'une des possibilités suivantes

puts(3.0/2)
puts(3/2.0)
puts(3.0/2.0)
puts(3.to_f/2)

Mais dans ce cas, on travaille sur des valeurs approchés. Pour avoir les valeurs exactes, il faut utiliser des fractions (voir à Mathématiques avec Python et Ruby/Fractions en Ruby). Et bien entendu, toute tentative de division par 0 donne un message d'erreur.

Reste[modifier | modifier le wikicode]

Lorsqu'on divise euclidiennement 13 par 8, le quotient est donc égal à 1. Mais il reste 5. Pour calculer directement ce reste en Ruby, on peut utiliser le symbole %:

a=13
b=8
r=a%b
puts(r)

Cette opération permet de travailler sur les congruences. Remarque: Si b=0, on a le même message d'erreur que lorsqu'on divise par 0.

Primalité[modifier | modifier le wikicode]

En Ruby, l'opération pgcd est infixée. Pour chercher le plus grand entier qui divise à la fois 13572468 et 12345678, on entre

a=13572468
b=12345678
g=a.gcd(b)
puts(g)

Bien entendu, a.gcd(b) et b.gcd(a) donnent le même résultat.

De même, le ppcm se calcule de façon analogue avec a.lcm(b).

Lorsqu'un entier n'a pas de diviseurs non triviaux, il est dit premier, et on a vu ci-dessus qu'avec mathn on peut tester si un nombre est premier. Avec prime aussi:

require 'prime'
n=2010
puts(n.prime?)
puts(n.prime_division)

Et en bonus, la décomposition en facteurs premiers!

Puissances[modifier | modifier le wikicode]

L'opérateur d'élévation à la puissance se note avec l'astérisque de la multiplication, mais dédoublée:

a=4
b=2
puts(a**b)
puts(b**a)

pour vérifier que (Exercice: Quelles sont les autres solutions de l'équation ?)

Remarques:

  1. Si l'exposant est négatif, le résultat est une fraction;
  2. Si l'exposant est réel, le résultat est réel aussi.

Priorités opératoires[modifier | modifier le wikicode]

En Ruby comme en algèbre, on effectue dans l'ordre

  1. Les parenthèses
  2. Les fonctions (comme l'élévation à une puissance)
  3. Les multiplications et divisions
  4. Les additions et soustractions.

Ainsi

puts(2+3*5)

affiche 17 et non 25: Les opérations ne sont pas effectuées de gauche à droite, mais en suivant les priorités opératoires.

Entiers et itération[modifier | modifier le wikicode]

Itérateur[modifier | modifier le wikicode]

La méthode la plus utile d'un nombre entier est sans conteste le bouclage, qui permet de répéter quelque chose de répétitif. Il suffit de dire à Ruby ce qu'il doit répéter (entre do et end) et combien de fois il doit le répéter: Un entier!

Bis repetita[modifier | modifier le wikicode]

Pour écrire un message très enthousiaste, on peut écrire

oui=10
oui.times do puts("Yes!") end

Avec un indice[modifier | modifier le wikicode]

Pour additionner les entiers successifs, on peut le faire avec

somme=0
n=10
n.times do |indice| somme+=indice end
puts(somme)

Le fait de mettre la variable entre traits verticaux lui donne automatiquement les valeurs entières successives. Mais la somme est affichée égale à 45 alors que 1+2+3+4+5+6+7+8+9+10=55...

Pour savoir d'où vient cette erreur de comptage, on peut essayer

n=10
n.times do |indice| puts(indice) end

Bingo! Les 10 premiers entiers naturels vont de 0 à 9, pas de 1 à 10.

Pour éviter de commencer par 0, on peut explicitement commencer par 1:

(1..10).inject {|somme, indice| somme+indice}

Mais cette fois-ci, on ne travaille plus avec un entier mais avec une liste d'entiers. Pour un tel objet, inject est une méthode typique de Ruby qui permet d'injecter à la liste un bloc d'instructions. Le bloc comprend deux variables locales, la première des deux (somme) étant destinée à se faire injecter des doses successives du médicament, la seconde (indice) représentant les doses de médicament à injecter l'une après l'autre.

Boucles[modifier | modifier le wikicode]

Ruby permet aussi de faire de la programmation impérative avec des boucles:

à nombre prédéterminé d'exécutions[modifier | modifier le wikicode]

Pour additionner les entiers de 1 à 10, on peut aussi faire comme ceci:

somme=0
for indice in 1..10 do
    somme+=indice
end
puts(somme)

Cette fois-ci on a bien 55.

à condition de sortie[modifier | modifier le wikicode]

La même somme peut aussi être calculée avec cette boucle:

somme,indice=0,0
while indice<=10 do
    somme+=indice
    indice=indice.succ
end
puts(somme)


Fractions en Ruby

"Dieu fit le nombre entier, le reste est l’œuvre de l'Homme", disait Leopold Kronecker. Le début du reste ce fut incontestablement les fractions, définies comme quotients d'entiers, et pratiquées bien avant les nombres décimaux.

Obtention d'une fraction[modifier | modifier le wikicode]

Pour entrer la fraction , on peut entrer

a=Rational(24,10)
puts(a)

qui la simplifie automatiquement. Alternativement, on peut charger mathn, ce après quoi le symbole de division donne des fractions au lieu de la division euclidienne:

require 'mathn'
a=24/10
puts(a)

On peut également obtenir une fraction à partir d'un réel, avec la méthode to_r (r comme rational). Mais la fraction n'est correcte que si son dénominateur est une puissance de 2:

a=1.2
b=a.to_r
puts(b)

Certes, mais tout de même...


Propriétés d'une fraction[modifier | modifier le wikicode]

Numérateur[modifier | modifier le wikicode]

Pour avoir le numérateur d'une fraction f, on entre f.numerator:

a=Rational(24,10)
puts(a.numerator)


Dénominateur[modifier | modifier le wikicode]

Pour avoir le dénominateur d'une fraction f, on entre f.denominator:

a=Rational(24,10)
puts(a.denominator)


Valeur approchée[modifier | modifier le wikicode]

Pour avoir la valeur approchée d'une fraction, on convertit celle-ci en un réel:

a=Rational(24,10)
puts(a.to_f)

Opérations sur les fractions[modifier | modifier le wikicode]

Opérations unaires[modifier | modifier le wikicode]

Opposé[modifier | modifier le wikicode]

L'opposé d'un nombre, en particulier d'une fraction, s'obtient en le faisant précéder d'un signe -:

a=Rational(2,-3)
puts(-a)

Inverse[modifier | modifier le wikicode]

Pour obtenir l'inverse d'une fraction, on divise 1 par celle-ci:

a=Rational(5,4)
puts(1/a)


Addition[modifier | modifier le wikicode]

Pour additionner deux fractions, on met le signe + entre elles, et le résultat est une fraction (même si celle-ci est entière, comme par exemple ):

a=Rational(34,21)
b=Rational(21,13)
puts(a+b)

Soustraction[modifier | modifier le wikicode]

La différence de deux fractions est une fraction :

a=Rational(34,21)
b=Rational(21,13)
puts(a-b)


Multiplication[modifier | modifier le wikicode]

Le produit de deux fractions est une fraction :

a=Rational(34,21)
b=Rational(21,13)
puts(a*b)


Division[modifier | modifier le wikicode]

Le quotient de deux fractions (à condition que la deuxième ne soit pas nulle) est une fraction :

a=Rational(34,21)
b=Rational(21,13)
puts(a/b)

Et même le reste euclidien est défini entre fractions, et le résultat est encore une fraction :

a=Rational(32,7)
b=Rational(7,2)
puts(a%b)


Exemple[modifier | modifier le wikicode]

On voudrait savoir quel est le rapport des longueurs des tuyaux d'orgue :

  1. Entre un gros Nasard et un Nasard;
  2. Entre un gros Nasard et une grosse Tierce.

Ces rapports sont affichés par Ruby sous forme de fractions, même le premier d'entre eux qui est entier (ce qui ne sautait pas aux yeux !) :

gn=5+Rational(1,3)
n=2+Rational(2,3)
gt=3+Rational(1,5)
puts(gn/n)
puts(gn/gt)

Puissance[modifier | modifier le wikicode]

Une puissance entière (même négative) d'une fraction) est encore une fraction :

a=Rational(3,2)
puts(a**12)
puts(a**(-2))

Mais si l'exposant est décimal, même si le résultat est une fraction, Ruby le considère comme un réel :

a=Rational(9,4)
b=a**0.5
puts(b)
puts(b.to_r)

Algorithmes[modifier | modifier le wikicode]

Réduite de Farey[modifier | modifier le wikicode]

Pour calculer la médiane (ou réduite) de Farey de deux fractions a et b, on définit une fonction Ruby de deux variables, qui s'appelle Farey :

def Farey(a,b)
    n=a.numerator+b.numerator
    d=a.denominator+b.denominator
    return Rational(n,d)
end


a=Rational(3,4)
b=Rational(1,13)
puts(Farey(a,b))

Fractions égyptiennes[modifier | modifier le wikicode]

Pour écrire une fraction à l'égyptienne (comme somme de fractions de numérateur 1), on applique l'algorithme de Fibonacci :

def egypt(f)
    e=f.to_i
    f-=e
    liste=[e]
    begin
        e=Rational(1,(1/f).to_i+1)
        f-=e
        liste.push(e)
    end while f.numerator>1
    liste.push(f)
    return liste
end

require 'mathn'

a=21/13
puts(egypt(a))

On peut résumer ce script Ruby aux étapes suivantes :

  1. On commence par extraire la partie entière de f, pour qu'il reste une fraction inférieure à 1 ;
  2. On soustrait à f (fraction restante) le plus grand inverse d'entier possible...
  3. On s'arrête quand le reste est lui-même un inverse d'entier (autrement dit, on continue tant que son numérateur est plus grand que 1).
  4. On ajoute à la liste, la dernière fraction obtenue.


Nombres réels en Ruby

Écriture décimale[modifier | modifier le wikicode]

Depuis l'apparition de chiffres arabes et de la numération de position, les nombres décimaux sont devenus plus concrets que les fractions: En écrivant , on voit deux nombres et on a tendance à oublier que cette écriture désigne un seul nombre (le quotient de 6 par 5). Alors qu'en écrivant ce nombre 1,2 on voit immédiatement qu'il n'y en a qu'un seul !

Decimaux[modifier | modifier le wikicode]

Un nombre décimal est un nombre dont le développement décimal s'arrête quelque part. Les réels non décimaux sont donc ceux dont le développement décimal est infini, et on peut en construire exprès de cette manière comme le fit Liouville par exemple.

En Ruby, certains nombres décimaux ont quand même une infinité de chiffres parce qu'ils sont stockés en machine sous forme binaire et que, sous cette forme, ils ont une infinité de chiffres.

Fractions[modifier | modifier le wikicode]

Le développement décimal d'une fraction se remarque par le fait qu'un motif finit par se répéter indéfiniment, comme le montrent les exemples suivants:

puts(1.0/3)
puts(1.0/9.0)
puts(1/11.0)
puts(1.0/7)

Nombres irrationnels[modifier | modifier le wikicode]

Les premiers nombres irrationnels connus ont été les racines carrées des nombres entiers et le nombre d'or.

puts(2**0.5)
puts(Math.sqrt(2))
puts((1+5**0.5)/2)
puts((Math.sqrt(5)+1)/2)

D'autres sont e et :

puts(Math::E)
puts(Math::PI)

Voici comment on peut calculer en Ruby la constante de Champernowne:

c='0.'
(1..40).collect { |n| c=c+n.to_s }
puts(c.to_f)
puts(c.to_r)


Le premier objet qui a été créé ci-dessus est une chaîne de caractères. Ruby le sait parce qu'on l'a mise entre guillemets. Initialement elle comprend le début de la représentation décimale de la constante (ou 0 suivi du point décimal). Le deuxième objet créé ci-dessus est une liste d'entiers (allant de 1 à 40). Cet objet est créé au vol, sans lui donner de nom, parce que la seule chose qu'on veuille faire avec lui, est d'appeler sa méthode collect qui fonctionne un peu comme le times des entiers: Un bloc d'instructions, entre accolades, avec un indice qui parcourt les éléments successifs du tableau, et ... une seule instruction, de concaténation de n (une fois transformé en chaîne de caractères avec to_s) et de la constante en cours de construction. Ceci fait, la constante est donc une chaîne de caratères, que Ruby peut transformer en un réel (flottant) ou en une fraction (rationnel).

Fonctions[modifier | modifier le wikicode]

Opérations[modifier | modifier le wikicode]

Les quatre opérations sont notées +, -, * et /. Dès que l'un des opérandes est écrit avec un point décimal, Ruby le reconnaît comme réel et l'opération donne un réel. La division peut même être euclidienne, ce qui permet notamment de calculer la valeur principale d'un angle en radians:

puts(100%Math::PI)

Le signe - peut aussi être unaire et dans ce cas, représente l'opposé du nombre qui le suit. Pour additionner un nombre h à un autre nombre x, on peut, au lieu de noter x=x+h, écrire x+=h.

Pour arrondir x à l'entier inférieur, on invoque x.floor; pour arrondir à l'entier supérieur, on invoque x.ceil. Pour calculer la valeur absolue de x, on invoque x.abs. Sa racine carrée se note indifféremment

r=2**0.5
puts(r)
r=Math.sqrt(2)
puts(r)

En effet, l'astérisque dédoublé code l'élévation à un exposant en Ruby.

Logarithmes et exponentielles[modifier | modifier le wikicode]

Logarithmes[modifier | modifier le wikicode]

Le script ci-dessous calcule et affiche l'image de 0,5 par le logarithme népérien, par le logarithme décimal, par les fonctions réciproques du cosinus hyperbolique, du sinus hyperbolique, de la tangente hyperbolique:

puts(Math.log(0.5))
puts(Math.log10(0.5))
puts(Math.acosh(0.5))
puts(Math.asinh(0.5))
puts(Math.atanh(0.5))


Exponentielles[modifier | modifier le wikicode]

Le script ci-dessous calcule et affiche l'image de 2 par l'exponentielle, par le cosinus hyperbolique, par le sinus hyperbolique, puis la tangente hyperbolique:

puts(Math.exp(2))
puts(Math.cosh(2))
puts(Math.sinh(2))
puts(Math.tanh(2))

Trigonométrie[modifier | modifier le wikicode]

Pour calculer les cosinus, sinus et tangente d'un radian, on peut faire comme ceci:

puts(Math.cos(1))
puts(Math.sin(1))
puts(Math.tan(1))

Pour connaître un angle en radians dont le cosinus, le sinus ou la tangente sont connus, on peut mettre un a devant la fonction:

puts(Math.acos(0.5))
puts(Math.asin(0.5))
puts(Math.atan(0.5))

Pour connaître un angle dont les côtés opposé et adjacent sont connus, on peut utiliser Math.atan(y/x) ou Math.atan2(x,y). Et même pour calculer , on peut utiliser Math.hypot(x,y). Par exemple, si on veut connaître les angles et l'hypoténuse d'un triangle rectangle de côtés 12 cm et 5 cm, on peut utiliser ce script:

cdr=180/Math::PI
a=12
b=5
puts(Math.atan2(a,b)*cdr)
puts(Math.atan2(b,a)*cdr)
puts(Math.hypot(a,b))


Nombres complexes en Ruby

On a inventé les nombres complexes juste parce qu'on voulait que certaines équations, comme , aient une solution (dans le cas présent, notée i). C'est typique des mathématiques, ça:

  1. On se fait un petit caprice;
  2. Pour le satisfaire, on invente une grosse théorie;
  3. D'autres gens utilisent cette théorie pour résoudre des problèmes, souvent issus des mathématiques appliquées, et qui n'ont plus rien de capricieux !

Instanciation d'un complexe[modifier | modifier le wikicode]

Pour créer dans Ruby le complexe x+iy, on utilise Complex(x,y):

a=Complex(4,3)
puts(a)

Les nombres x et y sont juste des nombres: Ils peuvent très bien être des entiers ou des fractions. On appelle entier de Gauss un nombre complexe dont les parties réelle et imaginaire sont des entiers relatifs.

Opérations[modifier | modifier le wikicode]

La somme, la différence, le produit et le quotient de deux nombres complexes sont des nombres complexes:

a=Complex(2,3)
b=Complex(4,3)
puts(a+b)
puts(a-b)
puts(a*b)
puts(a/b)

Ces exemples illustrent le fait que la somme, la différence et le produit d'entiers de Gauss sont des entiers de Gauss. Par contre leur quotient exact ne l'est pas forcément. L'exemple de la soustraction montre que même lorsque le résultat d'une opération est réel, Ruby le considère quand même comme un complexe (de partie imaginaire nulle).

Ainsi,

i=Complex(0,1)
puts(i**2)
puts(i**i)

On voit que pour Ruby, et non -1. On voit également que est réel. En effet, la puissance se note toujours ** en Ruby, et l'exposant n'est pas nécessairement réel.

Il est donc possible de calculer "la" racine carrée d'un complexe z avec z**0.5. Mais si 7+24i est le carré de deux complexes, "sa" racine carrée calculée par Ruby est 4+3i et non -4-3i. Comment Ruby choisit-il parmi ces deux nombres?

De même, -1 a deux racines carrées dans : i et -i. Ruby n'en reconnaît qu'une, et on se doutait qu'il choisirait i plutôt que son opposé. Mais

puts((-1)**0.5)
puts(Complex(-1,0)**0.5)

Si -1 est réel, il n'a pas de racine carrée du tout (sous-entendu dans ), alors que si -1 est complexe, "sa" racine carrée est proche de i mais pas exactement égale à i (sa partie réelle étant de l'ordre de ).

Propriétés[modifier | modifier le wikicode]

Les parties réelle et imaginaire d'un complexe a sont des propriétés de celui-ci:

a=Complex(4,3)
puts(a.real)
puts(a.imag)
puts(a.conj)

Il en est donc de même de son conjugué, qui est un complexe (contrairement à sa partie réelle, qui peut être un réel, mais aussi un entier ou une fraction, selon le cas).

D'autres propriétés d'un complexe sont son module et son argument:

a=Complex(4,3)
puts(a.abs)
puts(a.arg)
puts(a.polar)


z.polar permet d'avoir d'un coup le module et l'argument d'un complexe. Il permet de résoudre plus rapidement le problème vu dans le chapitre sur les nombres réels: Chercher le plus petit angle (en radians) et l'hypoténuse d'un triangle rectangle dont les côtés mesurent 12 cm et 5 cm:

a=12
b=5
z=Complex(a,b)
puts(z.polar)

Fonctions[modifier | modifier le wikicode]

CMath contient des versions complexes des fonctions trigonométriques, exponentielles et logarithmes:

Exponentielles[modifier | modifier le wikicode]

On peut retrouver l'écriture exponentielle des complexes de module 1 à condition de considérer l'exposant comme imaginaire (ci-dessous, pour vérifier numériquement que ):

require 'cmath'
t=Complex(0,Math::PI/3)
w=CMath.exp(t)
puts(w.real==0.5)
puts(w.real-0.5)
puts(w.imag==Math.sqrt(3)/2)

Comme d'habitude, 0,5 n'étant pas stocké très précisément en machine, l'égalité des parties réelles n'est qu'approximative.

Pour calculer les cosinus hyperbolique, sinus hyperbolique et tangente hyperbolique de 4+3i, on fait ainsi:

require 'cmath'
a=Complex(4,3)
puts(CMath.cosh(a))
puts(CMath.sinh(a))
puts(CMath.tanh(a))

Logarithmes[modifier | modifier le wikicode]

Pour calculer les images de 4+3i par les fonctions logarithme népérien, logarithme décimal, arguments des fonctions trigonométriques hyperboliques, on peut faire ainsi:

require 'cmath'
a=Complex(4,3)
puts(CMath.log(a))
puts(CMath.log10(a))
puts(CMath.acosh(a))
puts(CMath.asinh(a))
puts(CMath.atanh(a))

Fonctions trigonométriques[modifier | modifier le wikicode]

directes[modifier | modifier le wikicode]

Les cosinus, sinus et tangente d'un nombre complexe z se calculent avec le module cmath:

require 'cmath'
z=Complex(4,3)
puts(CMath.cos(z))
puts(CMath.sin(z))
puts(CMath.tan(z))

indirectes[modifier | modifier le wikicode]

Les fonctions trigonométriques inverses se calculent de manière analogue, en mettant juste un C devant Math:

require 'cmath'
z=Complex(4,3)
puts(CMath.acos(z))
puts(CMath.asin(z))
puts(CMath.atan(z))


La fonction arc tangente se calcule aussi avec deux nombres complexes:

require 'cmath'
a=Complex(4,3)
b=Complex(2,1)
puts(CMath.atan2(a,b))

Ça doit sûrement servir à quelque chose, mais à quoi?


Quaternions et octonions en Ruby

Complexes[modifier | modifier le wikicode]

On a vu dans le chapitre précédent que pour Ruby, un nombre complexe z est essentiellement une structure abritant deux réels, accessibles par z.real et z.imag respectivement. La construction de Cayley-Dickson généralise ce point de vue: En prenant deux complexes a et b, on peut les regrouper dans une nouvelle structure qui est considérée comme un nombre: Un quaternion.

Dans toute la suite, on va profiter de la gestion des fractions offerte par cmath, avec

require 'cmath'

Quaternions[modifier | modifier le wikicode]

Definition et affichage[modifier | modifier le wikicode]

Définition[modifier | modifier le wikicode]

La définition d'un quaternion se fait dans une classe nommée Quaternion:

class Quaternion

end

La première méthode, l'initialisation, crée donc deux variables a et b (qui seront des complexes, mais Ruby ne le sait pas encore):

Initialisation[modifier | modifier le wikicode]
	def initialize(a,b)
		@a,@b = a,b
	end

Les nombres complexes a et b seront des propriétés du quaternion:


Propriétés a et b[modifier | modifier le wikicode]
	def a
		@a
	end
	
	def b
		@b
	end

Désormais on accède aux deux complexes a et b d'un quaternion q par q.a et q.b.

Affichage[modifier | modifier le wikicode]

Pour afficher un quaternion q avec puts(q), il est nécessaire de redéfinir (une sorte de surcharge) sa méthode de conversion en chaîne de caractères (string):

	def to_s
		'('+a.real.to_s+')+('+a.imag.to_s+')i+('+b.real.to_s+')j+('+b.imag.to_s+')k'
	end

La notation des points se lit de droite à gauche, par exemple a.real veut dire la partie réelle de a et q.a.real, la partie réelle du a de q.

Le quaternion de Ruby ne possède alors que deux propriétés, a et b, mais on va se rattraper sur les méthodes, qui opèrent sur un quaternion (ou deux):

Fonctions[modifier | modifier le wikicode]

Module[modifier | modifier le wikicode]

Le module d'un quaternion est un réel:

	def abs
		Math.hypot(@a.abs,@b.abs)
	end


Conjugué[modifier | modifier le wikicode]

Le conjugué d'un quaternion est un quaternion de même module que celui-ci:

	def conj
		Quaternion.new(@a.conj,-@b)
	end

Opérations[modifier | modifier le wikicode]

Addition[modifier | modifier le wikicode]

Pour additionner deux quaternions, on additionne leurs a respectifs, et leurs b respectifs, et on crée un nouveau quaternion à partir des deux nombres complexes obtenus:

	def +(q)
		Quaternion.new(@a+q.a,@b+q.b)
	end

Pour calculer et afficher la somme des quaternions p et q, il suffit alors d'entrer puts(p+q).

Soustraction[modifier | modifier le wikicode]

La soustraction des quaternions relève d'un principe analogue:

	def -(q)
		Quaternion.new(@a-q.a,@b-q.b)
	end

Multiplication[modifier | modifier le wikicode]

Le produit de deux quaternions est plus difficile à définir:

	def *(q)
		Quaternion.new(@a*q.a-@b*q.b.conj,@a*q.b+@b*q.a.conj)
	end

La multiplication des quaternions n'est pas commutative, comme le montre l'exemple suivant:

p=Quaternion.new(Complex(2,1),Complex(3,4))
q=Quaternion.new(Complex(2,5),Complex(-3,-5))
puts(p*q)
puts(q*p)

Division[modifier | modifier le wikicode]

Pour diviser un quaternion par un autre, on peut faire ainsi:

	def /(q)
		d=q.abs**2
		Quaternion.new((@a*q.a.conj+@b*q.b.conj)/d,(-@a*q.b+@b*q.a)/d)
	end

Comme ils ont le même module, le quotient d'un quaternion par son conjugué est égal à 1:

p=Quaternion.new(Complex(2,1),Complex(3,4))

puts((p/p.conj).abs)

Cet exemple révèle que , c'est-à-dire que , qui est une décomposition de comme somme de 4 carrés.

Résumé[modifier | modifier le wikicode]

La classe Quaternion de Ruby tient en entier dans un fichier plutôt léger, au vu de ses possibilités:

require 'cmath'

class Quaternion

	def initialize(a,b)
		@a,@b = a,b
	end
	
	def a
		@a
	end
	
	def b
		@b
	end
	
	def to_s
		'('+a.real.to_s+')+('+a.imag.to_s+')i+('+b.real.to_s+')j+('+b.imag.to_s+')k'
	end
	
	def +(q)
		Quaternion.new(@a+q.a,@b+q.b)
	end
	
	def -(q)
		Quaternion.new(@a-q.a,@b-q.b)
	end
	
	def *(q)
		Quaternion.new(@a*q.a-@b*q.b.conj,@a*q.b+@b*q.a.conj)
	end
	
	def abs
		Math.hypot(@a.abs,@b.abs)
	end
	
	def conj
		Quaternion.new(@a.conj,-@b)
	end
	
	def /(q)
		d=q.abs**2
		Quaternion.new((@a*q.a.conj+@b*q.b.conj)/d,(-@a*q.b+@b*q.a.conj)/d)
	end

end

Si on enregistre ce fichier sous le nom quaternions.rb, il suffit d'insérer require 'quaternions' pour être en mesure d'effectuer des calculs sur les quaternions.


Octonions[modifier | modifier le wikicode]

Ce qui est intéressant avec la construction de Cayley-Dickson utilisée ci-dessus pour les quaternions, c'est qu'elle se généralise: En définissant une structure (un objet) comprenant deux quaternions a et b, on définit un octonion.

Définition et affichage[modifier | modifier le wikicode]

Définition[modifier | modifier le wikicode]

Comme pour les quaternions, on décrit l'objet octonion dans une classe Octonion:

class Octonion

	def initialize(a,b)
		@a,@b = a,b
	end
	
	def a
		@a
	end
	
	def b
		@b
	end

Au passage on définit les propriétés a et b de l'octonion comme celles du quaternion, sauf que cette fois-ci ce ne sont plus des complexes mais des quaternions. Mais comme Ruby est faiblement typé, cette particularité n'apparaîtra que lorsque a ou b sera utilisé.

Affichage[modifier | modifier le wikicode]

Là encore, la méthode to_s se définit comme celle des quaternions, mais il y a 8 nombres à afficher au lieu de 4:

	def to_s
		'('+a.a.real.to_s+')+('+a.a.imag.to_s+')i+('+a.b.real.to_s+')j+('+a.b.imag.to_s+')k+('+b.a.real.to_s+')l+('+b.a.imag.to_s+')li+('+b.b.real.to_s+')lj+('+b.b.imag.to_s+')lk'
	end

Pour accéder au premier de ces nombres, que est la partie réelle du a de a, on note a.a.real. Autrement dit, on parcourt un arbre binaire, de profondeur 3.

Fonctions[modifier | modifier le wikicode]

Les fonctions sur les octonions se définissent presque comme celles sur les quaternions, Cayley-Dickson oblige:

Module[modifier | modifier le wikicode]

Comme pour les quaternions:

	def abs
		Math.hypot(@a.abs,@b.abs)
	end

Conjugué[modifier | modifier le wikicode]

	def conj
		Octonion.new(@a.conj,Quaternion.new(0,0)-@b)
	end

Opérations[modifier | modifier le wikicode]

Addition[modifier | modifier le wikicode]

Comme pour les quaternions, on additionne les octonions composante par composante (a avec o.a, b avec o.b):

	def +(o)
		Octonion.new(@a+o.a,@b+o.b)
	end

Soustraction[modifier | modifier le wikicode]

	def -(o)
		Octonion.new(@a-o.a,@b-o.b)
	end

Multiplication[modifier | modifier le wikicode]

	def *(o)
		Octonion.new(@a*o.a-o.b*@b.conj,@a.conj*o.b+o.a*@b)
	end

Non seulement la multiplication des octonions n'est pas commutative, elle n'est plus associative non plus:

m=Octonion.new(p,q)
n=Octonion.new(q,p)
o=Octonion.new(p,p)
puts((m*n)*o)
puts(m*(n*o))

Division[modifier | modifier le wikicode]

	def /(o)
		d=1/o.abs**2
		Octonion.new((@a*o.a.conj+o.b*@b.conj)*Quaternion.new(d,0),(Quaternion.new(0,0)-@a.conj*o.b+o.a.conj*@b)*Quaternion.new(d,0))
	end

Là encore, le quotient d'un octonion par son conjugué est de module 1:

puts(m/m.conj)
puts((m/m.conj).abs)

Résumé[modifier | modifier le wikicode]

L'objet Octonion de Ruby est lui aussi, assez léger:

class Octonion

	def initialize(a,b)
		@a,@b = a,b
	end
	
	def a
		@a
	end
	
	def b
		@b
	end
	
	def to_s
		'('+a.a.real.to_s+')+('+a.a.imag.to_s+')i+('+a.b.real.to_s+')j+('+a.b.imag.to_s+')k+('+b.a.real.to_s+')l+('+b.a.imag.to_s+')li+('+b.b.real.to_s+')lj+('+b.b.imag.to_s+')lk'
	end
	
	def +(o)
		Octonion.new(@a+o.a,@b+o.b)
	end
	
	def -(o)
		Octonion.new(@a-o.a,@b-o.b)
	end
	
	def *(o)
		Octonion.new(@a*o.a-o.b*@b.conj,@a.conj*o.b+o.a*@b)
	end
	
	def abs
		Math.hypot(@a.abs,@b.abs)
	end
	
	def conj
		Octonion.new(@a.conj,Quaternion.new(0,0)-@b)
	end
	
	def /(o)
		d=1/o.abs**2
		Octonion.new((@a*o.a.conj+o.b*@b.conj)*Quaternion.new(d,0),(Quaternion.new(0,0)-@a.conj*o.b+o.a.conj*@b)*Quaternion.new(d,0))
	end
	

end

En l'enregistrant sous le nom octonions.rb, il suffit d'écrire

require 'octonions'

pour être en mesure d'effectuer des calculs sur les octonions en Ruby.

Bibliographie[modifier | modifier le wikicode]

  • En fait, les quaternions existent déjà sous Ruby, à condition de les télécharger: [1]; sur le même site, l'auteur propose aussi des octonions.
  • Sur les octonions, le livre de John Baez est une lecture hautement conseillée: [2]


Ruby et probabilités

Comme tout langage de programmation qui se respecte, Ruby permet de calculer des nombres pseudoaléatoires, et donc de faire de la simulation de phénomènes aléatoires.

Mais parmi les objets que gère Ruby, il y a aussi les ensembles, donc les évènements.


Ensembles en Ruby

Les évènements sont décrits en probabilité par des ensembles. Si ces ensembles sont finis, Ruby les gère.

Construction d'évènements[modifier | modifier le wikicode]

Évènements certain et impossible[modifier | modifier le wikicode]

L'évènement impossible, noté , est entré en Ruby avec des crochets vides:

impossible=[]
puts(impossible.length())
puts(impossible.empty?)

L'ensemble de toutes les issues possibles de l'expérience aléatoire, appelé univers, est noté .

Pour savoir si un élément se trouve dans un ensemble, on utilise include? comme dans omega.include?(6) pour savoir si l'évènement peut donner un 6.

Avec un dé[modifier | modifier le wikicode]

On s'apprête à lancer un dé. Alors l'évènement "le résultat sera plus petit que 5" est décrit par l'ensemble . De même, l'évènement "le résultat sera pair" est représenté par . On construit aisément ces évènements avec la notation ensembliste de Ruby qui se fait avec des crochets au lieu des accolades. Mais la définition d'ensembles par description est possible avec Ruby:

univers=(1..6).to_a
petit=univers.select { |r| r<5 }
pair=univers.select { |r| r%2==0 }
impossible=[]

Pour construire l'univers, on peut prend la liste des nombres allant de 1 à 6, et on la transforme en tableau avec to_a. Pour avoir les petits résultats (moins que 5), on choisit dans l'univers les éléments qui sont inférieurs à 5. select est une méthode de l'objet univers, qui se crée une variable r (entre traits verticaux) et lui fait parcourir les éléments du tableau (car univers en est un) et lui fait passer ou non par un filtre. En bref, on sélectionne les éléments de l'univers qui sont inférieurs à 5. De même, pour avoir les résultats pairs, on sélectionne les éléments de l'univers dont le quotient par 2 tombe juste (reste nul).

Avec des cartes[modifier | modifier le wikicode]

Cette fois-ci, on extrait au hasard une carte parmi un jeu de 32 cartes.

On construit l'univers par un produit cartésien (des cartes avec Descartes !) entre l'ensemble des valeurs et celui des couleurs:

valeurs=[1,7,8,9,10,'Valet','Dame','Roi']
couleurs=['carreau','cœur','pique','trèfle']
valeurs=valeurs.collect { |v| v.to_s }
univers=[]
valeurs.collect { |v| couleurs.collect { |c| univers.push(v+' '+c) }}
puts(univers)

La troisième ligne transforme toutes les cartes en chaînes de caractères; en effet certaines d'entre elles étaient des chiffres. La suite du script consiste à créer un univers initialement vide, puis, avec la méthode collect du tableau des valeurs de cartes, à placer dans le jeu de cartes, l'une après l'autre, toutes les cartes (il est nécessaire de mettre une boucle à l'intérieur de la première, pour les différentes couleurs associées à chaque valeur de carte).

L'évènement "la carte est une figure" (pas un nombre) se construit en choisissant les cartes dont le début du nom n'est pas un nombre entier (donc se transforme en 0 lorsqu'on le convertit en entier):

figure=univers.select { |carte| carte[0..1].to_i==0 }
puts(figure)

Et pour construire l'évènement "la carte est un pique", on extrait les cartes de pique du jeu entier:

pique=univers.select { |carte| carte[-2..-1]=='ue'}
puts(pique)

(on extrait les cartes dont le nom termine par ue, puisque seul le mot pique se termine ainsi).

Évènements simultanés[modifier | modifier le wikicode]

Notation[modifier | modifier le wikicode]

L'évènement "A et B" se note , et l'opération se note en Ruby par une esperluette (&).

Avec le dé[modifier | modifier le wikicode]

petit=[1,2,3,4]
pair=[2,4,6]
puts(petit&pair)

Avec les cartes[modifier | modifier le wikicode]

Le script suivant montre que dans un jeu de 32 cartes, il y en a 3 qui sont à la fois des figures et des piques: Les trois figures de pique:

puts(figure&pique)


Le "ou" inclusif[modifier | modifier le wikicode]

Notation[modifier | modifier le wikicode]

De même l'évènement "A ou B" se note , et en Ruby, le symbole pipe (trait vertical). Ruby enlève automatiquement les doublons.

Avec le dé[modifier | modifier le wikicode]

petit=[1,2,3,4]
pair=[2,4,6]
puts(petit|pair)

Avec les cartes[modifier | modifier le wikicode]

On peut compter les cartes qui sont des figures ou des piques:

puts(figure|pique)
puts((figure|pique).size)

...mais on peut aussi laisser Ruby les compter, il en trouve 17. Ce comptage est à la base des calculs de probabilité.

Contraire[modifier | modifier le wikicode]

Pour calculer le contraire d'un évènement, on le soustrait à l'univers.

Avec le dé[modifier | modifier le wikicode]

univers=[1,2,3,4,5,6]
petit=[1,2,3,4]
pair=[2,4,6]
puts(univers-petit)
puts(univers-pair)

Ce qui montre que le contraire de "pair" est "impair".

Avec les cartes[modifier | modifier le wikicode]

puts(univers-figure)
puts(univers-pique)

Probabilités[modifier | modifier le wikicode]

La probabilité d'un évènement est définie comme le quotient de sa taille (en Ruby, size) par celle de l'univers.

On peut associer une probabilité à un évènement seul, en baptisant l'univers $univers ce qui fait qu'il est une variable globale, et en définissant une probabilité par

require 'mathn'
def proba(e)
    return Rational(e.size,$univers.size)
end

Mais pour éviter l'usage d'une variable globale, on peut aussi associer une probabilité à l'évènement et à son univers:


Avec le dé[modifier | modifier le wikicode]

require 'mathn'
univers=[1,2,3,4,5,6]
petit=[1,2,3,4]
pair=[2,4,6]
puts(Rational(petit.size,univers.size))
puts(Rational(pair.size,univers.size))


def proba(e,u)
    return Rational(e.size,u.size)
end

p1=proba(petit,univers)+proba(pair,univers)-proba(petit&pair,univers)
puts(p1)
p2=proba(petit|pair,univers)
puts(p1==p2)


Avec les cartes[modifier | modifier le wikicode]

require 'mathn'

puts(Rational(figure.size,univers.size))
puts(Rational(pique.size,univers.size))


def proba(e,u)
    return Rational(e.size,u.size)
end

p1=proba(figure,univers)+proba(pique,univers)-proba(figure&pique,univers)
puts(p1)
p2=proba(figure|pique,univers)
puts(p1==p2)

Probabilités conditionnelles[modifier | modifier le wikicode]

Ci-dessus, on a défini les probabilités avec comme paramètre l'univers. En effet, cette variable est globale, donc inaccessible a priori dans le corps de la fonction. Ceci permet de remplacer l'univers par un autre évènement, et donc de définir la probabilité conditionnelle. Par exemple, avec le dé:

require 'mathn'
univers=[1,2,3,4,5,6]
petit=[1,2,3,4]
pair=[2,4,6]

def proba(e,u)
    return Rational(e.size,u.size)
end

p1=proba(petit&pair,petit)
puts(p1)
p2=proba(petit&pair,pair)
puts(p1==p2)

Définitions[modifier | modifier le wikicode]

On peut alors définir des booléens concernant des évènements, en utilisant les propriétés de leurs probabilités:

require 'mathn'

def incompatibles(a,b)
    return proba(a&b)==0
end


def indépendants(a,b)
    return proba(a&b)==proba(a)*proba(b)
end


Nombres pseudo-aléatoires en Ruby

Nombres pseudo-aléatoires[modifier | modifier le wikicode]

Le traditionnel nombre pseudo-aléatoire compris entre 0 et 1 s'obtient avec

puts(rand)

Si on veut un nombre entier aléatoire, on peut mettre le nombre d’occurrences possibles entre parenthèses après le rand. Par exemple, pour lancer un dé à 6 faces, on obtient avec rand(6) un nombre entre 0 et 5. Donc pour lancer un dé, on fait

puts(rand(6).succ)

Pour simuler une variable aléatoire binomiale de paramètres n et p, on peut utiliser le fait que celle-ci est la somme de n variables de Bernoulli indépendantes entre elles. Façon Ruby, cela peut se faire avec l'algorithme suivant (basé sur une analogie avec un jeu de pile ou face, où p est la probabilité d'avoir pile et n le nombre de lancers de la pièce):

  1. On crée un ensemble de n objets, par exemple une liste de nombres (1..n);
  2. On extrait de celle-ci les nombres victorieux (ceux pour lesquels une pièce est tombée sur pile);
  3. On compte les objets retenus.

En Ruby cela donne

def binomial(n,p)
    return ((1..n).select { |i| rand<p }).size
end


10.times do puts(binomial(8,0.2)) end

Lancer de dés[modifier | modifier le wikicode]

Un dé[modifier | modifier le wikicode]

Pour voir si le dé est équilibré, on peut le lancer quelques milliers de fois et compter combien de fois chaque face est sortie... ou laisser faire le travail par Ruby:

effectifs=[0]*6
n=6000
n.times do effectifs[rand(6)]+=1 end
puts(effectifs)

Deux dés[modifier | modifier le wikicode]

Pour lancer deux dés et additionner leurs résultats, on fait comme ci-dessus et on additionne. Seulement le tableau des effectifs est indexé de 0 à 10 (2 de moins que les résultats des lancers):

effectifs=[0]*11
n=6000
n.times do effectifs[rand(6)+rand(6)]+=1 end
puts(effectifs)

Avec des cartes[modifier | modifier le wikicode]

Tirer une carte au hasard[modifier | modifier le wikicode]

Puisque le jeu de cartes est un tableau, il suffit de choisir un indice au hasard pour tirer une carte au hasard. Par prudence, on va faire semblant de ne pas savoir qu'il y a 32 cartes dans le jeu (et qu'elles sont numérotées de 0 à 31). On va tirer 3200 fois une carte d'un jeu de 32 et compter le nombre de fois qu'on a eu un as de pique. Pour cela on va utiliser un compteur du nombre de victoires (gains) et on va lui injecter une unité chaque fois que le nom de la carte est 1 pique:

valeurs=[1,7,8,9,10,'Valet','Dame','Roi']
valeurs=valeurs.collect { |v| v.to_s}
couleurs=['carreau','cœur','pique','trèfle']
univers=[]
valeurs.collect{|v| couleurs.collect{|c| univers.push(v+' '+c)}}

n=3200
gains=(1..n).inject {|g,i| g+(univers[rand(univers.size)]=='1 pique' ? 1 : 0) }
puts(gains.to_f/n)

Tirer 5 cartes au hasard[modifier | modifier le wikicode]

Pour constituer une main de 5 cartes, il suffit a priori de faire 5 fois l'opération précédente:

valeurs=[1,7,8,9,10,'Valet','Dame','Roi']
valeurs=valeurs.collect { |v| v.to_s}
couleurs=['carreau','cœur','pique','trèfle']
univers=[]
valeurs.collect{|v| couleurs.collect{|c| univers.push(v+' '+c)}}

hand=[]
((1..5).to_a).collect{hand.push(univers[rand(univers.size)])}
puts(hand)

Seulement il peut arriver qu'on ait deux fois l'as de pique dans la même main ! En effet le script précédent réalise un tirage avec remise, pour lequel les calculs de probabilités sont plus faciles, mais irréaliste pour les jeux de cartes et le Loto. Ce dont on a besoin dans le cas présent, c'est d'un tirage sans remise, qui se produira en enlevant les cartes au fur et à mesure qu'on les met dans la main de 5 cartes.

valeurs=[1,7,8,9,10,'Valet','Dame','Roi']
valeurs=valeurs.collect { |v| v.to_s}
couleurs=['carreau','cœur','pique','trèfle']
univers=[]
valeurs.collect{|v| couleurs.collect{|c| univers.push(v+' '+c)}}

hand=[]
while hand.size<5
    jeu=univers-hand
    carte=jeu[rand(jeu.size)]
    hand.push(carte)
end

puts(hand)

Le jeu dont on a extrait les cartes est une variable locale, et à la fin de ce script il y a toujours 32 cartes dans l'univers.

Ensuite on peut compter les carrés d'as parmi 10 000 parties:

valeurs=[1,7,8,9,10,'Valet','Dame','Roi']
valeurs=valeurs.collect { |v| v.to_s}
couleurs=['carreau','cœur','pique','trèfle']
univers=[]
valeurs.collect{|v| couleurs.collect{|c| univers.push(v+' '+c)}}
 
carres=0
10000.times do
    hand=[]
    while hand.size<5
        jeu=univers-hand
        carte=jeu[rand(jeu.size)]
        hand.push(carte)
    end
    if (hand.select {|c| c[0..1]=='1 '}).size==4
        carres+=1
    end
end
 
puts(carres.to_f/10000)

On construit une liste avec les cartes de hand dont le nom commence par un 1 suivi d'un espace (les as), et lorsque la longueur de cette liste est égale à 4, on incrémente le compteur carres. À la fin de l'exécution du script, on a donc le nombre de carrés d'as parmi les 10 000 tirages, et en le divisant par 10 000, on a la fréquence de ces carrés d'as. Elle est très faible !

Mélanger un jeu de cartes[modifier | modifier le wikicode]

En 1741, dans les Mémoires de l'Académie des Sciences de Berlin, Leonhard Euler publiait un article titré Calcul de la probabilité du jeu de rencontre. Le nom initial du jeu de rencontre était jeu de treize parce qu'il se jouait à 13 cartes. Mais Euler généralise le jeu à n cartes.

Il le définit ainsi:

Le jeu de rencontre est un jeu de hasard, où deux personnes ayant chacune un entier jeu de cartes, en tirent à la fois une carte après l'autre, jusqu'à ce qu'il arrive, qu'elles rencontrent la même carte: et alors l'une des deux personnes gagne. Or, lorsqu'une telle rencontre n'arrive point du tout, alors c'est l'autre des deux personnes qui gagne. Cela posé, on demande la probabilité, que l'une et l'autre de ces deux personnes aura de gagner.

Dans cet excellent article (16 pages en Français),

Euler montre que

Pourvu donc que le nombre de cartes ne soit pas moindre que 12, l'espérance de A sera toujours à celle de B à peu près comme 12 à 7 ... Ou bien parmi 19 jeux qu'on joue, il y en aura probablement 12 qui font gagner A, et 7 qui feront gagner B.

Pour mélanger un jeu de cartes, on peut construire une main de 32 cartes ! Ensuite on peut répéter l'expérience 1900 fois, et compter combien de fois il y a eu au moins une rencontre (le nombre de rencontres strictement positif), et enfin, par division par 1900, estimer la fréquence de ces rencontres, et la comparer avec le quotient de 12 par 19 donné par Euler:

valeurs=[1,7,8,9,10,'Valet','Dame','Roi']
valeurs=valeurs.collect { |v| v.to_s}
couleurs=['carreau','cœur','pique','trèfle']
univers=[]
valeurs.collect{|v| couleurs.collect{|c| univers.push(v+' '+c)}}
 
gains=0
1900.times do
    hand=[]
    while hand.size<32
        jeu=univers-hand
        carte=jeu[rand(jeu.size)]
        hand.push(carte)
    end
    rencontres=((0..31).to_a).select { |i| hand[i]==univers[i] }
    if rencontres.size>0
        gains+=1
    end
end
 
puts(gains.to_f/1900)
puts(12.0/19)

Un exemple de résultat sur 1900 jeux:

0.634736842105263

0.631578947368421

La valeur exacte de la probabilité d'une rencontre est donnée par Euler, dont la formule se traduit en Ruby par

a=(1..32).inject {|p,n| p+(-1)**n/(1..n).inject(1) {|f,k| f*k}}
puts(1-1/a)

et qui donne la valeur ...

Méthode de Monte-Carlo[modifier | modifier le wikicode]

Pour calculer la valeur approchée de par la méthode de Monte-Carlo, on crée un nuage de points à coordonnées pseudo-aléatoires, et on compte combien d'entre eux sont à moins d'une unité de distance de l'origine du repère:

points=(1..1000000).inject{|p,n| p+(Math.hypot(rand,rand)<1? 1 : 0) }
puts(points.to_f/1000000*4)

Le résultat est correct à trois décimales près.


Ruby et analyse


Suites en Ruby

Une suite de nombres (éventuellement complexes) ne peut se représenter en machine parce qu'elle comprend une infinité de termes. Alors on n'en représente qu'une partie sous forme de liste de nombres. Et Ruby manipule très bien ce genre d'objets.

Définition de suites[modifier | modifier le wikicode]

Par fonction[modifier | modifier le wikicode]

Une suite est une fonction de dans (ou ...). On peut donc facilement calculer les premiers termes de celle-ci en utilisant la méthode collect d'une liste d'entiers (approximation finie de ). Par exemple pour vérifier que la suite tend vers 0, on peut essayer

(1..50).collect{|n| puts(1/n.to_f)}

Suites récurrentes[modifier | modifier le wikicode]

Pour une suite récurrente, chaque terme est défini à partir du précédent.

Suite logistique[modifier | modifier le wikicode]

La suite logistique est chaotique sur [0;1]. Pour le vérifier, on peut faire

u=0.1
50.times do
    u=4*u*(1-u)
    puts(u)
end

En constatant que , on peut vérifier que, quoique chaotique, cette suite est formée de fractions:

require 'mathn'
u=1/10
10.times do
    u=4*u*(1-u)
    puts(u)
end

Quoique chaotique, cette suite ne fait pas un bon générateur pseudo-aléatoire, parce que les nombres proches de 0 et 1 sont trop souvent visités. Pour le vérifier graphiquement, on peut dessiner un histogramme des 4000 premières valeurs de la suite, avec l'algorithme suivant:

  1. On fait comme ci-dessus, mais au lieu d'afficher u, on incrémente l'entrée d'un tableau des effectifs indexée par sa troncature. C'est ce tableau qui va être représenté graphiquement.
  2. Ensuite, on représente chaque effectif par un rectangle de largeur 4 pixels et de hauteur l'effectif correspondant. Les rectangles sont bleus, remplis de vert.

Le tout est fait en écrivant les instructions dans le langage svg, engendrées par Ruby, dans un fichier HistogramRuby1.svg visible ci-dessous. Voici le script au complet:

figure=File.open("HistogramRuby1.svg","w")
figure.puts('<?xml version="1.0" encoding="utf-8"?>')
figure.puts('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"')
figure.puts('"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">')
figure.puts('<svg xmlns="http://www.w3.org/2000/svg" width="500" height="360">')

effectifs=[0]*100
u=0.1

4000.times do
    u=4*u*(1-u)
    effectifs[(100*u).to_i]+=1
end

(0..99).collect{|n| figure.puts('<rect x="'+(4*n+50).to_s+'" y="'+(320-effectifs[n]).to_s+'" width="4" height="'+effectifs[n].to_s+'" fill="green" stroke="blue" stroke-width="1" />')}

figure.puts('</svg>')
figure.close

Et voici le fichier produit par le script:


Suites arithmétiques et géométriques[modifier | modifier le wikicode]

Les suites arithmétiques et géométriques sont aussi des suites récurrentes.

Suites arithmétiques[modifier | modifier le wikicode]

Une suite est arithmétique de raison r si . Cette définition est récurrente.

Par exemple, si on place 2000 € avec des intérêts (simples) correspondant à 3 % du capital de départ, soit 60 €, on peut calculer les valeurs successives du capital pendant 20 ans avec

capital=2000.00
interet=capital*3/100.0
20.times do
    capital+=interet
    puts(capital)
end

Suites géométriques[modifier | modifier le wikicode]

Une suite est géométrique de raison r si . Les suites géométriques sont donc aussi récurrentes.

Si on place 2000 € à intérêts composés au taux de 2 % par an, l'affichage des valeurs successives du capital (arrondies à l'eurocent près) peut se faire avec

capital=2000.00
20.times do
    capital*=1.02
    puts(capital.round(2))
end

Sachant que chaque humain a deux parents et que chacun d'entre eux a aussi deux parents, etc. on peut dire que le nombre d'ancêtres à la génération n est géométrique de raison 2. Le nombre total d'ancêtres jusqu'à la génération n s'obtient par

(0..20).inject{|ancetres,generation| ancetres+=2**generation}

puts(ancetres)
puts(2**21)

On a presque autant d'ancêtres à la génération 21 qu'à toutes les générations précédentes cumulées!

Ce qui donne envie de vérifier si c'est pareil pour toutes les générations ou si c'est une spécificité de la génération 21:

genealogie=[1]*20
(1..20).collect{|i| genealogie[i]=(0..i).inject{|a,g| a+=2**g }}
generations=[1]*20
(1..20).collect{|i| generations[i]=2**i}
test=(1..20).reject{|i| generations[i]==genealogie[i-1]+2}
puts(test.size)

On crée une liste des ancêtres jusqu'à la génération n comprise (genealogie) et une liste (generations) des nombres d'ancêtres à la génération n seulement. La lise test est constituée des entiers n pour lesquels le nombre d'ancêtres à la génération n n'est pas égal au total d'ancêtres jusqu'à la génération n-1 augmenté de 2 (avec reject, on a enlevé les positifs). La longueur de ce test est très petite !

Sous Ruby on peut aussi calculer des suites géométriques de raison complexe. La somme des termes est alors particulièrement intéressante à étudier (par exemple si la raison vaut i).

Suites d'entiers[modifier | modifier le wikicode]

Suite de Fibonacci[modifier | modifier le wikicode]

Calcul des termes[modifier | modifier le wikicode]

La récurrence de la suite de Fibonacci est double, avec . Son calcul pose donc un problème algorithmique, puisqu'il faut trois variables (les deux termes à calculer et une variable tampon pour stocker temporairement l'un des deux termes, afin qu'il ne soit pas écrasé par la somme). Ce problème n'existe pas en Ruby qui permet les affectations simultanées:

a=1
b=1
for n in 1..20 do
    a,b=b,a+b
    puts(b)
end

On peut aussi le faire de manière plus Ruby:

a=1
b=1
(1..20).collect{
    a,b=b,a+b
    puts(b)
}

Nombre d'Or[modifier | modifier le wikicode]

Pour étudier le quotient de deux termes successifs de la suite:

a,b=1,1
(1..20).collect{
    a,b=b,a+b
    puts(b.to_f/a.to_f)
}

puts((5**0.5+1)/2)

Mais en fait, les nombres de Fibonacci étant entiers, leurs quotients sont des fractions, et cette variante le montre:

require 'mathn'

a,b=1,1
(1..20).collect{
    a,b=b,a+b
    puts(b/a)
}

On a donc une suite d'approximations rationnelles du nombre d'Or.

Suite de Collatz[modifier | modifier le wikicode]

Algorithmiquement, la suite de Collatz est intéressante parce que son calcul est basé sur un test de parité, et qu'elle utilise une boucle à condition de sortie:

def Collatz(x)
    if x%2==0
        return x/2
    else
        return 3*x+1
    end
end

u=65
while(u>1) do
    u=Collatz(u)
    puts(u)
end

Multiples communs[modifier | modifier le wikicode]

La suite des multiples de 5 et la suite des multiples de 7 sont arithmétiques de raisons respectives 5 et 7. On peut les construire en choisissant les nombres entiers qui sont divisibles respectivement par 5 et par 7:

a5=(1..1000).select{|n| n%5==0}
a7=(1..1000).select{|n| n%7==0}
puts(a5&a7)

Les multiples communs à 5 et 7 sont les multiples de 35, qui est le ppcm de 5 et 7. Cette construction est à l'origine de la théorie des idéaux par Kummer.

Suites et séries[modifier | modifier le wikicode]

Une série est une suite dont le terme général est défini par une somme.

Premier exemple[modifier | modifier le wikicode]

La suite définie par tend vers 1, il est relativement aisé de le démontrer, et encore plus facile de le vérifier avec Ruby:

suite=(1..50).collect{|n|
    (1..n).inject(0){|somme,k|
        somme+=1.0/(k*k.succ)
    }
}

puts(suite)

Cette suite est une suite de rationnels:

require 'mathn'

suite=(1..20).collect{|n|
    (1..n).inject(0){|somme,k|
        somme+=1/(k*k.succ)
    }
}

puts(suite)

Cette variante suggère d'ailleurs une démonstration de la convergence, grâce à l'émission d'une conjecture sur le terme général de la suite...

Deuxième exemple[modifier | modifier le wikicode]

La suite converge aussi, bien que ce ne soit pas évident en voyant son expression algébrique.

suite=(1..20).collect{|n|
    (1..n).inject(0){|somme,k|
        somme+=n.to_f/(n**2+k)
    }
}

puts(suite)

Là encore, la suite est rationnelle:

require 'mathn'

suite=(1..20).collect{|n|
    (1..n).inject(0){|somme,k|
        somme+=n/(n**2+k)
    }
}

puts(suite)


Constante d'Euler[modifier | modifier le wikicode]

On peut la calculer (et vérifier la lenteur de la convergence) avec

suite=(1..50).collect{|n|
    (1..n).inject(0){|s,k| s+=1.0/k}-Math.log(n)
}

puts(suite)

Applications[modifier | modifier le wikicode]

Méthode de Heron[modifier | modifier le wikicode]

Pour calculer avec la méthode de Heron, on utilise la suite itérée :

u=1.0
50.times do
    u=(u+5.0/u)/2.0
    puts(u)
end

Mais encore une fois, cette suite qui converge vers est formée de fractions. On a donc une suite d'approximations rationnelles de :

require 'mathn'

u=1
50.times do
    u=(u+5/u)/2
    puts(u)
end

On en déduit des approximations rationnelles du nombre d'Or :

require 'mathn'

u=1
10.times do
    u=(u+5/u)/2
    puts((u+1)/2)
end

En comparant avec les quotients de nombres de Fibonacci successifs, on voit que la méthode de Heron converge beaucoup plus vite. Cette convergence peut se montrer en représentant graphiquement la suite, ce qu'on peut faire en plaçant des points dans un fichier svg:

figure=File.open("SuiteRuby01.svg","w")
figure.puts('<?xml version="1.0" encoding="utf-8"?>')
figure.puts('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"')
figure.puts('"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">')
figure.puts('<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480">')

figure.puts('<line x1="20.0" y1="460.0" x2="540.0" y2="460.0" style="stroke:rgb(0,0,64);stroke-width:1"/>')
((0..400).select {|x| x%10==0}).collect { |x| figure.print('<text x="'+(x+20).to_s+'" y="475.0" style="font-size:6;fill:rgb(0,0,64);font-weight:normal">'+(x/10).to_s+'</text>\n'+'<line x1="'+(x+20).to_s+'" y1="455" x2="'+(x+20).to_s+'" y2="465" style="stroke:rgb(0,0,64);stroke-width:1"/>\n')}
figure.puts('<line x1="20.0" y1="460.0" x2="20.0" y2="60.0" style="stroke:rgb(0,40,0);stroke-width:1"/>')
((0..300).select {|x| x%10==0}).collect { |x| figure.print('<text x="0" y="'+(460-x).to_s+'" style="font-size:6;fill:rgb(0,40,0);font-weight:normal">'+(x/100.0).to_s+'</text>\n'+'<line x1="18" y1="'+(460-x).to_s+'" x2="22" y2="'+(460-x).to_s+'" style="stroke:rgb(0,40,0);stroke-width:1"/>\n')}

u=1.0
n=0
40.times do
    n+=1
    u=(u+5.0/u)/2.0
    figure.puts('<circle cx="'+(20+10*n).to_s+'" cy="'+(460-100*u).to_s+'" r="2" fill="white" stroke="red" stroke-width="1" />')
end

figure.puts('</svg>')
figure.close

Le fichier produit par ce script s'appelle SuiteRuby01.svg. Le voici:

Formule de l'arc tangente[modifier | modifier le wikicode]

p=(1..100).collect{|n|
    4*(0..n).inject(0){|s,k|
        s+=(-1)**k/(2*k+1.0)
    }
}

puts(p)

Comme on le voit, la suite converge très lentement.

Encore une fois, les termes de la suite sont rationnels, ce qui donne une suite de fractions approchant :

require 'mathn'

p=(1..10).collect{|n|
    4*(0..n).inject(0){|s,k|
        s+=(-1)**k/(2*k+1)
    }
}

puts(p)


Fonctions en Ruby

En Ruby, une fonction s'appelle une méthode, mais du point de vue mathématique, ce n'est guère qu'une question de vocabulaire.

Exemple[modifier | modifier le wikicode]

Sujet Bac STG CGRH Métropole-Réunion Septembre 2007[modifier | modifier le wikicode]

Extrait du sujet de l'exercice 3:
Une entreprise produit des appareils électroménagers. Le coût horaire de production de x appareils est donné en euros par :

pour

Et plus bas, dans le même exercice,

le coût moyen est défini par
Le coût moyen de production d’un objet est égal à

pour x appartenant à [5 ; 40].

Définition des méthodes[modifier | modifier le wikicode]

Donc pour Ruby, C et f seront des méthodes, dont l'antécédent s'appellera x et dont l'image sera envoyée par return:

def C(x)
    return x**2+50*x+100.0
end


def f(x)
    return C(x)/x
end

Bien entendu, on pouvait aussi définir f directement par

def f(x)
    return x+50+100.0/x
end


Tableau de valeurs[modifier | modifier le wikicode]

Suite de l'énoncé du Bac STG 2007[modifier | modifier le wikicode]

Par la suite, on demande de reproduire et compléter le tableau suivant, arrondi au centième d'euro:

x 5 10 20 30 40
f(x)

Tableau de valeurs[modifier | modifier le wikicode]

Certes, avec une boucle, on peut calculer plein de valeurs de f(x) différentes:

for x in 5..40 do
    puts("l'image de #{x} par f est #{f(x)}")
end

mais on a trop de valeurs de x pour remplir le tableau. Une meilleure variante sera donc

for x in [5,10,20,30,40] do
    puts("l'image de #{x} par f est #{f(x)}")
end

qui est déjà bien plus léger à exploiter.

Plus typiquement Ruby:

[5,10,20,30,40].collect {|x| puts(f(x))}

Image d'un ensemble par une fonction[modifier | modifier le wikicode]

Pour que la fonction f ne s'applique plus seulement à un réel, mais à tous les nombres du tableau, on utilise map (une méthode de l'objet tableau):

[5,10,20,30,40].map { |x| puts(f(x)) }

Cette notion d'image d'un ensemble par une fonction est à la base de pratiquement toute la géométrie: Alors que la symétrie centrale est définie comme une transformation qui associe un point à un point, on l'applique très vite à des ensembles de points comme dans l'expression la symétrique d'une droite par rapport à un point.

Arrondis[modifier | modifier le wikicode]

Enfin, pour avoir les arrondis au centime près:

[5,10,20,30,40].collect {|x| puts(f(x).round(2))}

qui remplit le tableau presque immédiatement.

Représentation graphique[modifier | modifier le wikicode]

Ruby n'étant pas très doué (pour le moment) en dessin, on va utiliser ses talents littéraires pour fabriquer un fichier au format svg. Ce fichier sera fabriqué comme une chaîne de caractères, et inscrit dans un fichier nommé FonctionRuby01.svg posté ci-dessous. Le fichier sera créé en mode écriture (w) avec

figure=File.open("FonctionRuby01.svg","w")

Création de la figure[modifier | modifier le wikicode]

On commence par écrire dans le fichier (qui, on l'a vu ci-dessus, s'appelle figure), ce qu'il faut pour dire que c'est un fichier svg (l'entête):

figure.puts('<?xml version="1.0" encoding="utf-8"?>')
figure.puts('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"')
figure.puts('"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">')
figure.puts('<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480">')

Le fichier fera donc 640 pixels de large et 480 pixels de haut.

Axes[modifier | modifier le wikicode]

Les axes et leurs graduations seront des traits, créés par des boucles puisque le travail des graduations est répétitif et que les boucles sont faites pour tout ce qui est répétitif.

Des abscisses[modifier | modifier le wikicode]

L'axe des abscisses et ses graduations sera bleu:

figure.puts('<line x1="20.0" y1="460.0" x2="540.0" y2="460.0" style="stroke:rgb(0,0,64);stroke-width:1"/>')
((0..500).select {|x| x%100==0}).collect { |x| figure.print('<text x="'+(x+20).to_s+'" y="475.0" style="font-size:16;fill:rgb(0,0,64);font-weight:normal">'+(x/10).to_s+'</text>\n'+'<line x1="'+(x+20).to_s+'" y1="455" x2="'+(x+20).to_s+'" y2="465" style="stroke:rgb(0,0,64);stroke-width:1"/>\n')}
((0..500).select {|x| x%50==0}).collect { |x| figure.print('<line x1="'+(x+20).to_s+'" y1="456" x2="'+(x+20).to_s+'" y2="464" style="stroke:rgb(0,0,64);stroke-width:1"/>\n')}
((0..500).select {|x| x%10==0}).collect { |x| figure.print('<line x1="'+(x+20).to_s+'" y1="458" x2="'+(x+20).to_s+'" y2="462" style="stroke:rgb(0,0,64);stroke-width:1"/>\n')}

Des ordonnées[modifier | modifier le wikicode]

L'axe des ordonnées est en vert:

figure.puts('<line x1="20.0" y1="460.0" x2="20.0" y2="40.0" style="stroke:rgb(0,40,0);stroke-width:1"/>')
((0..400).select {|y| y%40==0}).collect { |y| figure.print('<text x="0" y="'+(460-y).to_s+'" style="font-size:16;fill:rgb(0,40,0);font-weight:normal">'+(y/4).to_s+'</text>\n'+'<line x1="15" y1="'+(460-y).to_s+'" x2="25" y2="'+(460-y).to_s+'"  style="stroke:rgb(0,40,0);stroke-width:1"/>\n')}
((0..400).select {|y| y%20==0}).collect { |y| figure.print('<line x1="16" y1="'+(460-y).to_s+'" x2="24" y2="'+(460-y).to_s+'"  style="stroke:rgb(0,40,0);stroke-width:1"/>\n')}
((0..400).select {|y| y%4==0}).collect { |y| figure.print('<line x1="18" y1="'+(460-y).to_s+'" x2="22" y2="'+(460-y).to_s+'"  style="stroke:rgb(0,40,0);stroke-width:1"/>\n')}

Courbe[modifier | modifier le wikicode]

On va représenter graphiquement la fonction en rouge, sous forme d'un polygone à 350 côtés:

(50..400).collect {|x| figure.print('<line x1="'+(19+x).to_s+'" y1="'+(460-4*f((x-1).to_f/10)).to_s+'" x2="'+(20+x).to_s+'" y2="'+(460-4*f(x.to_f/10)).to_s+'"  style="stroke:rgb(120,0,0);stroke-width:1"/>\n')}

Résultat[modifier | modifier le wikicode]

Pour que la figure soit reconnue, il reste encore à fermer la balise svg, ouvert au tout début de la figure, puis à fermer le fichier pour que Ruby le libère:

figure.puts('</svg>')

figure.close

L'exécution du script ci-dessus produit le fichier suivant:

Certes, le fichier Ruby a l'air encore plus compliqué que le fichier svg mais il est possible de créer des méthodes axes et trait pour en simplifier la lecture.


Analyse numérique en Ruby

Fonction[modifier | modifier le wikicode]

Les algorithmes ci-dessous seront appliqués à la fonction f : . On va donc commencer par créer une méthode pour cela:

def f(x)
    return x**2-5
end

Résolution numérique d'une équation[modifier | modifier le wikicode]

Pour chercher à près un antécédent de 0 par f, on peut utiliser la méthode de dichotomie:

def zerof(a,b)
    if f(a)*f(b)>0 then
        puts('Pas de solution unique entre '+a.to_s+' et '+b.to_s+'.')
    else
        while ((a-b).abs>1e-14)
            m=(a+b)/2.0
            if f(m)*f(a)>0 then
                a=m
            else
                b=m
            end
        end
    end
    return m
end


puts(zerof(1,3))

Le script affiche une solution parce que f(1) est négatif et f(3) positif. Sinon on aurait un message d'erreur.

Calcul approché d'un nombre dérivé[modifier | modifier le wikicode]

On approche la tangente par une sécante. On utilise une méthode centrée:

def NDerf(x)
    h=1e-10
    return (f(x+h)-f(x-h))/(2*h)
end

puts(NDerf(2))

On voit que

Calcul approché d'une intégrale[modifier | modifier le wikicode]

La méthode des rectangles consiste à approcher par la somme des aires des rectangles de largeur h et de hauteur f(a+nh) pour a+nh allant de a à b. On choisit N assez grand (ici 1 000 000) pour que h soit petit et l'approximation bonne:

def Nintf(a,b)
    h=(b-a).to_f/1e6
    return (1..1000000).inject{|s,i| s+=h*f(a+h*i)}
end

puts(Nintf(0,2))


Ruby et géométrie


Points en Ruby

L'objet Point est une bonne manière d'aborder la programmation objet. En géométrie repérée, un point est constitué de deux nombres, son abscisse et son ordonnée.


Voici l'énoncé de l'exercice:
Dans un repère orthonormé, on considère , et . Calculer les distances AB, AC et BC et en déduire la nature du triangle ABC. Puis en déduire les coordonnées du centre de son cercle circonscrit, et le rayon de celui-ci.

Classe[modifier | modifier le wikicode]

Pour que Ruby possède un objet Point, il suffit de le définir, sous la forme d'une classe:

class Point

    def initialize(x,y)
        @x, @y = x, y
    end

end

Dorénavant, chaque fois qu'on crée un point par Point.new(x,y), celui-ci possédera les coordonnées x et y qui sont pour l'instant ses seules propriétés (des variables stockées temporairement dans l'objet).

Coordonnées[modifier | modifier le wikicode]

Cependant pour accéder depuis l'extérieur aux coordonnées du point, il faut les redéfinir comme des méthodes Ruby (parce que dans Ruby, tout est méthode).

Abscisse[modifier | modifier le wikicode]

Il suffit de dire que la méthode x renvoit le nombre x:

    def x
        @x
    end

(à l'intérieur de la classe)

Ordonnée[modifier | modifier le wikicode]

Idem pour y:

    def y
        @y
    end

Dorénavant, l'abscisse de P s'appelle P.x et son ordonnée, P.y.

Affichage[modifier | modifier le wikicode]

Pour afficher un objet, il faut utiliser la conversion to_s que Ruby propose. Dans le cas présent, puisqu'on a inventé un nouvel objet, on doit redéfinir cette conversion en chaîne de caractères en mettant les coordonnées entre parenthèses, séparées par un point-virgule:

    def to_s
        '('+@x.to_s+';'+@y.to_s+')'
    end

Pour afficher un point M, on peut faire

puts(M.to_s)

mais aussi

puts(M)

puisque Ruby se charge automatiquement de la conversion to_s.

Deux points[modifier | modifier le wikicode]

Le plus simple avec deux points, c'est le milieu, parce que c'est un objet de même type (un point):

Milieu[modifier | modifier le wikicode]

    def milieu(q)
        Point.new((@x+q.x)/2,(@y+q.y)/2)
    end

La syntaxe est typique de Ruby: On parle de "milieu avec q" en invoquant

puts(p.milieu(q))

Vecteur[modifier | modifier le wikicode]

Un peu hors sujet ici (on en reparlera dans le chapitre qui leur est consacré), le vecteur est bel et bien associé à deux points: son origine A et son extrémité B. Et ses coordonnées se calculent à partir de celles de A et de B:

    def vecteur(q)
        Vecteur.new(q.x-@x,q.y-@y)
    end

Ce qui oblige, soit à placer la classe vecteur dans le même fichier, soit à l'importer avec

require 'vector'

si on a enregistré ladite classe dans un fichier vector.rb.

Là encore, on parle de méthode vecteur jusqu'à q pour un point p.

Distance[modifier | modifier le wikicode]

La distance jusqu'à q est un nombre, mais associé à deux points:

    def distance(q)
        (self.vecteur(q)).norme
    end

Pour faire le plus simple possible, on a là encore utilisé le fichier des vecteurs sous la forme de sa méthode norme: La distance AB est la norme du vecteur , qu'on calcule avec la fonction hypot de Ruby (voir au chapitre suivant comment on l'utilise).

Pour calculer la distance entre p et q, on entre

puts(p.distance(q))

ou, au choix,

puts(q.distance(p))

Application au problème[modifier | modifier le wikicode]

Pour récapituler, la classe Point en entier est décrite ici:

class Point

    def initialize(x,y)
        @x, @y = x, y
    end
    
    def x
        @x
    end

    def y
        @y
    end

    def to_s
        '('+@x.to_s+';'+@y.to_s+')'
    end

    def milieu(q)
        Point.new((@x+q.x)/2,(@y+q.y)/2)
    end

    def vecteur(q)
        Vecteur.new(q.x-@x,q.y-@y)
    end

    def distance(q)
        (self.vecteur(q)).norme
    end

end

C'est tout!

On commence par créer trois points, les sommets du triangle:

a=Point.new(-1,3)
b=Point.new(5,1)
c=Point.new(1,5)

Nature de ABC[modifier | modifier le wikicode]

Pour voir si ABC est isocèle, on peut afficher les longueurs de ses côtés:

puts(a.distance(b))
puts(a.distance(c))
puts(b.distance(c))

mais on n'apprend pas grand-chose (sinon qu'il n'est pas isocèle). Mais on peut chercher s'il est rectangle avec la réciproque du théorème de Pythagore:

puts(a.distance(b)**2)
puts(a.distance(c)**2+b.distance(c)**2)

C'est clair, le triangle ABC est rectangle en C.

Centre du cercle[modifier | modifier le wikicode]

Il en résulte alors que le cercle circonscrit a pour diamètre [AB], donc pour centre le milieu M de [AB] et pour rayon la moitié de AB:

m=a.milieu(b)

puts(m)

Rayon du cercle[modifier | modifier le wikicode]

Le rayon peut se calculer en divisant par 2 la distance AB, ou mieux, en vérifiant que les trois rayons MA, MB et MC ont la même longueur à la précision permise par Ruby:

puts(m.distance(a))
puts(m.distance(b))
puts(m.distance(c))

Figure[modifier | modifier le wikicode]

On peut résumer le tout en modifiant le script ci-dessus pour qu'il crée des éléments svg, dont le résultat est visible ci-dessous:


Vecteurs en Ruby

Un vecteur peut être défini par ses deux coordonnées ou avec deux points (son origine et son extrémité). La seconde technique a été abordée dans le chapitre précédent; on utilisera donc ici la définition par les coordonnées:

Définition[modifier | modifier le wikicode]

Le vecteur sera donc ici une classe Vecteur:

class Vecteur

    def initialize(x,y)
        @x, @y = x, y
    end

end

Créer un vecteur, c'est donc lui donner une abscisse et une ordonnée. Comme on l'a vu dans le chapitre précédent, on peut maintenant en créer en Ruby avec

v=Vecteur.new(2,3)

Coordonnées[modifier | modifier le wikicode]

On gère les coordonnées et l'affichage comme avec les points; il y a beaucoup de ressemblance entre les vecteurs et les points, ce sont les méthodes qui ne seront pas les mêmes.

Abscisse[modifier | modifier le wikicode]

    def x
        @x
    end

Pour lire ou modifier l'abscisse d'un vecteur u, on invoque u.x.

Ordonnée[modifier | modifier le wikicode]

    def y
        @y
    end

Affichage[modifier | modifier le wikicode]

    def to_s
        '('+@x.to_s+';'+@y.to_s+')'
    end

Il suffit pour afficher un vecteur u, de faire

puts(u)

Norme[modifier | modifier le wikicode]

La norme d'un vecteur se calcule avec le théorème de Pythagore:

    def norme
        Math.hypot(@x,@y)
    end

On a utilisé la norme d'un vecteur pour calculer des distances au chapitre précédent.

Opérations[modifier | modifier le wikicode]

Il n'est pas d'usage de calculer le milieu de deux vecteurs, mais par contre, on n'additionne pas les points d'habitude (sauf avec GeoGebra). Mais les vecteurs, eux, on les additionne:

Somme[modifier | modifier le wikicode]

La somme de deux vecteurs est définie par la somme des coordonnées:

    def + (u)
        Vecteur.new(@x+u.x,@y+u.y)
    end

La notation est infixée ce qui fait que la somme de deux vecteurs u et v se note tout simplement u+v.

Produits[modifier | modifier le wikicode]

Il y a deux multiplications intéressantes:

Par un nombre[modifier | modifier le wikicode]

En multipliant un vecteur par un nombre, on obtient un vecteur:

    def * (r)
        Vecteur.new(@x*r,@y*r)
    end

Seulement on est obligé de mettre le nombre après le vecteur (u*3 pour avoir le triple de u, alors que d'habitude on fait plutôt le contraire: 3u), et ce produit est moins intéressant que le suivant:

Par un vecteur[modifier | modifier le wikicode]

En multipliant un vecteur par un vecteur, on obtient un nombre. Comme les nombres sont disposés comme les barreaux d'une échelle, on appelle cette multiplication, le produit scalaire des deux vecteurs:

    def * (u)
        @x*u.x+@y*u.y
    end

On écrit u*v pour avoir le produit scalaire de u par v.

Tests[modifier | modifier le wikicode]

De colinéarité[modifier | modifier le wikicode]

Pour savoir si deux vecteurs sont colinéaires, on compare deux produits:

    def colin(u)
        @x*u.y==@y*u.x
    end

Pour savoir si u et v sont colinéaires, on entre

puts(u.colin(v))

qui donnera true ou false selon que les vecteurs sont, ou non, colinéaires.

D'orthogonalité[modifier | modifier le wikicode]

Pour savoir si deux vecteurs sont orthogonaux, on compare leur produit scalaire à 0:

    def ortho(u)
        self * u ==0
    end

Exemple[modifier | modifier le wikicode]

Dans l'exemple du chapitre précédent, le produit scalaire permet plus rapidement de vérifier que le triangle ABC est rectangle:

a=Point.new(-1,3)
b=Point.new(5,1)
c=Point.new(1,5)


u=c.vecteur(a)
v=c.vecteur(b)


puts(u*v)
puts(u.ortho(v))


Droites en Ruby

La droite peut être définie à partir d'une de ses équations, mais aussi à partir de deux points. Et comme on a vu précédemment comment on peut créer en Ruby un objet point, on va voir comment on peut s'en servir pour gérer des droites sous Ruby.

Définition[modifier | modifier le wikicode]

Là encore, on va définir une classe Droite possédant, lors de son instanciation, deux points:

class Droite

    def initialize(a,b)
        @a,@b=a,b
    end

Vecteurs[modifier | modifier le wikicode]

Vecteur directeur[modifier | modifier le wikicode]

    def directeur
        @a.vecteur(@b)
    end

On obtient le vecteur directeur de d par d.directeur

Alignement[modifier | modifier le wikicode]

Pour savoir si le point m est sur la droite d, on peut rajouter ce test:

    def IsOnLine(d)
        vecteur(d.a).colin(d.directeur)
    end

mais on le rajoute dans l'objet Point, puisque c'est une propriété du point...

Vecteur normal[modifier | modifier le wikicode]

Le vecteur normal s'obtient en choisissant ses coordonnées pour que le produit scalaire avec le vecteur directeur soit nul:

    def normal
        Vecteur.new(-self.directeur.y,self.directeur.x)
    end

Le vecteur normal s'obtient avec d.normal et permet facilement d'avoir l'équation cartésienne ci-dessous.

Équations[modifier | modifier le wikicode]

Équation cartésienne[modifier | modifier le wikicode]

    def cartesienne
        '('+self.normal.x.to_s+')x+('+self.normal.y.to_s+')y='+(self.normal.x*@a.x+self.normal.y*@a.y).to_s
    end

Pour afficher l'équation cartésienne de d, on entre

puts(d.cartesienne)

Équation réduite[modifier | modifier le wikicode]

Comme il y a des divisions à effectuer, on a intérêt à faire appel à mathn':

require 'mathn'


Coefficient directeur[modifier | modifier le wikicode]

    def cd
        self.directeur.y/self.directeur.x
    end

Ordonnée à l'origine[modifier | modifier le wikicode]

    def oalo
        @a.y-self.cd*@a.x
    end

Équation[modifier | modifier le wikicode]

L'équation réduite se définit par

    def reduite
        'y='+self.cd.to_s+'x+('+self.oalo.to_s+')'
    end

et s'obtient par d.reduite.

Comparaison de deux droites[modifier | modifier le wikicode]

Parallélisme[modifier | modifier le wikicode]

Deux droites sont parallèles lorsque leurs vecteurs directeurs sont colinéaires. Mais aussi (sous réserve qu'elles en aient) lorsqu'elles ont le même coefficient directeur:

    def parallele(d)
        self.cd==d.cd
    end

Pour savoir si deux droites d1 et d2 sont parallèles, on fait

puts(d1.parallele(d2))

Perpendicularité[modifier | modifier le wikicode]

Deux droites sont perpendiculaires si et seulement si leurs vecteurs normaux sont orthogonaux:

    def perpendiculaire(d)
        self.normal.ortho(d.normal)
    end

On aurait aussi pu chercher si le produit de leurs coefficients directeurs est égal à -1.

Intersection[modifier | modifier le wikicode]

Pour calculer les coordonnées du point d'intersection de deux droites, on résout un système.

Exemple[modifier | modifier le wikicode]

Dans l'exemple des chapitres précédents, on peut regarder si les deux droites (CA) et (CB) sont perpendiculaires:

a=Point.new(-1,3)
b=Point.new(5,1)
c=Point.new(1,5)


d1=Droite.new(c,a)
d2=Droite.new(c,b)
puts(d1.perpendiculaire(d2))


Résolution de problèmes en Ruby


Résolution de systèmes en Ruby

Un petit exercice
Au village de Trokhafairtrade dans le Swazibwana occidental, on pratique un mélange de commerce équitable et de troc. Ainsi, un habitant a acquis 2 youkoulélés d'Hawaii contre 3 xylophones et 1 € et un écotouriste a acquis un xylophone et un youkoulélé pour la modique somme de 8 €. On demande le prix, en euros, d'un xylophone et le prix d'un youkoulélé sur le marché de Trokhafairtrade.

En notant x le prix d'un xylophone et y celui d'un youkoulélé, l'énoncé se traduit algébriquement par 2y=3x+1 et x+y=8.

On va donc voir comment résoudre le système de deux équations à deux inconnues suivant:

Méthode itérative[modifier | modifier le wikicode]

Dans le cas présent, il se trouve que x et y sont entiers naturels. Si on sait que c'est le cas, on peut les chercher par tâtonnement avec une boucle sur x et sur y. On va successivement fabriquer un tableau bidimensionnel avec les entiers de 0 à 100 (deux premières lignes) puis regarder quels couples de ce tableau vérifient à la fois les deux conditions données par le système:

total=[[]]
(0..100).each{|x| (0..100).each{|y| total.push([x,y])}}
solutions=total.select{|c| 3*c[0].to_f-2*c[1].to_f==-1 and c[0].to_f+c[1].to_f==8}
puts(solutions)

Méthode algébrique[modifier | modifier le wikicode]

Le système peut aussi s'écrire matriciellement soit avec et . Alors sa solution s'obtient par le calcul matriciel , ce qui se fait directement avec le module matrix de Ruby:

require 'matrix'
require 'mathn'

A=Matrix[[3,-2],[1,1]]
B=Matrix[[-1],[8]]
solution=A.inverse*B
puts(solution)

En ayant choisi mathn avec, les solutions s'écrivent automatiquement sous forme de fractions si elles ne sont pas entières.


Triplets pythagoriciens en Ruby

L'énoncé du problème est simple, sa solution avec Ruby aussi:

Énoncé

Trouver tous les triplets pythagoriciens (x,y,z) tels que ; autrement dit, on demande les triangles rectangles de périmètre 1000 dont les côtés sont entiers.

(1..1000).collect{|y| (1..y).collect{|x| z=Math.hypot(x,y) ; puts(x,y,z) if x+y+z==1000}}

On apprend qu'il n'y a qu'un triplet de somme 1000.


Systèmes congruentiels en Ruby

Rallye mathématique de la Réunion[modifier | modifier le wikicode]

Sujet 2005, Exercice 2[modifier | modifier le wikicode]

Énoncé
Pour organiser une grande manifestation sportive, le professeur d’éducation physique doit rassembler sur le stade un important groupe d’élèves. Le nombre d’élèves est compris entre 2 800 et 2 900. Il en profite pour leur faire remarquer que, regroupés par 2, puis par 3, puis par 4, puis par 5, puis par 6, il en reste toujours 1 ; mais, ô miracle, en se regroupant par 7, il ne reste personne.

On demande combien d'élèves il y a au total.

On va filtrer les solutions avec les critères donnés par l'énoncé, l'un après l'autre.

Intervalle[modifier | modifier le wikicode]

les nombres à tester sont tous les nombres entre 2800 et 2900, il y en a donc 101:

solutions=(2800..2900).to_a

puts(solutions.size)

Par 2[modifier | modifier le wikicode]

On ne va finalement garder que les nombres qui, pris 2 par 2, laissent 1 (soit tels que le reste de la division par 2 donne 1; on rappelle que ce reste se note par le symbole pourcent).

solutions=solutions.select{|n| n%2==1}

puts(solutions.size)

Ah! Il n'en reste plus que 50 à tester!

Par 3[modifier | modifier le wikicode]

Parmi ces 50, on va garder uniquement ceux qui, pris 3 par 3 aussi, laissent 1:

solutions=solutions.select{|n| n%3==1}

puts(solutions.size)

De 50, on est passé à 17.

Par 4[modifier | modifier le wikicode]

Ensuite on garde ceux des 17 qui, divisés par 4, laissent 1:

solutions=solutions.select{|n| n%4==1}

puts(solutions.size)

Plus que 8...

Par 5[modifier | modifier le wikicode]

On continue l'épuration:

solutions=solutions.select{|n| n%5==1}

puts(solutions)

Plus que deux candidats possibles !

Par 6[modifier | modifier le wikicode]

solutions=solutions.select{|n| n%6==1}

puts(solutions)

Pas de changement, toujours deux candidats.

Par 7[modifier | modifier le wikicode]

Maintenant on veut garder les nombres divisibles par 7. Le reste euclidien devra donc être nul:

solutions=solutions.select{|n| n%7==0}

puts(solutions)

Et hop! On a le nombre d'élèves!

Sujet 2007, Exercice 3[modifier | modifier le wikicode]

Énoncé
Chaque semaine, Jean ramasse entre 40 et 200 œufs qu’il va vendre au marché.

Ce soir, veille de marché, il est perplexe.

• S’il met ses œufs dans des emballages de 6, il en reste 2.

• S’il utilise des emballages de 10, il en reste encore 2.

• Il me faudrait, dit-il, des emballages de 8 pour tout contenir exactement.

On demande combien il y a d'œufs en tout.

Même exercice que celui d'au-dessus:

Intervalle[modifier | modifier le wikicode]

solutions=(40..200).to_a

puts(solutions.size)


Par 6[modifier | modifier le wikicode]

solutions=solutions.select{|n| n%6==2}

puts(solutions.size)

Plus que 27 nombres à tester.

Par 10[modifier | modifier le wikicode]

solutions=solutions.select{|n| n%10==2}

puts(solutions)

Plus que 5 nombres à tester.

Par 8[modifier | modifier le wikicode]

On veut que le reste dans la division par 8 soit nul:

solutions=solutions.select{|n| n%8==0}

puts(solutions)

Un seul nombre a réussi le parcours du combattant jusqu'au bout: C'est la solution au problème.


Freudenthal sous Ruby

Le problème de Freudenthal est intéressant à traiter en Ruby parce qu'il peut se résoudre en manipulant des tableaux et ensembles et que pour Ruby, les tableaux peuvent se manipuler comme des ensembles et vice-versa. Le problème est d'ailleurs intéressant en soi parce qu'il porte sur la logique épistémique. En voici l'énoncé traduit du Néerlandais à l'Anglais puis au Français:

Énoncé
On choisit au hasard deux entiers x et y strictement supérieurs à 1, x étant le plus petit des deux, et on donne à Sam leur somme qui est inférieure à 100, et à Polly leur produit. Après un temps de réflexion suffisamment long, le dialogue suivant se déroule entre Sam et Polly:
  • Polly: Je ne sais pas qui sont x et y.
  • Sam: Je savais que tu ne savais pas!
  • Polly: Alors je sais qui sont x et y.
  • Sam: Alors je sais aussi!

Le but du problème est donc d'utiliser les connaissances qu'on a sur les connaissances de Polly et Sam pour savoir si on peut savoir quels sont x et y.

Il y a de nombreuses méthodes pour y arriver, et Ruby est en quelque sorte un langage "multiparadigme" où chacun peut utiliser sa propre logique (épistémique ou non) pour aborder le problème.

Digression arithmétique[modifier | modifier le wikicode]

Si on avait confié un nombre premier à Polly, elle n'aurait pas avoué son impuissance lors de sa première affirmation. Bien que ce ne soit nullement nécessaire pour résoudre ce problème, Ruby sait depuis la version 1.9 manipuler des nombres premiers. Avec

puts(Prime.prime?(p))

on peut savoir si p est premier, et avec

puts(Prime.prime_division(n))

on décompose n en facteurs premiers.

Les produits à partir des sommes[modifier | modifier le wikicode]

Plutôt que de manipuler des couples d'entiers x et y possibles, on va, comme dans la version Python, manipuler l'ensemble des produits possibles. Donc on aura besoin de l'outil permettant, à partir d'une somme s donnée à Sam, d'obtenir la liste prod(s) des produits donnés à Polly qui correspondent aux mêmes x et y:

def prod(n)
    t=[]
    (2..n-2).each{|x| t.push(x*(n-x))}
    return t.uniq
end

On peut décrire l'algorithme ci-dessus ainsi:

  1. On crée un tableau vide t
  2. Pour chaque nombre entre 2 et n-2, noté provisoirement x, on rajoute le produit de x par son complément à n dans le tableau t
  3. Quand on a fini, on transforme t en ensemble, en enlevant les doublons.

Première affirmation[modifier | modifier le wikicode]

L'affirmation apporte une information: Le produit donné à Polly peut s'obtenir de plusieurs manières, sinon Polly connaîtrait les facteurs. Pour exploiter cette information, on va commencer par fabriquer l'énorme liste des produits possibles, puis ne garder que ceux qui apparaissent au moins deux fois dans la liste:

On ramasse les y entre 3 et 100; pour chacun d'entre eux on ramasse (collect) les x entre 2 et y-1; si la somme x+y est inférieure ou égale à 100, on rajoute le produit dans le tableau produit. Ensuite on constitue pour chaque p de ce tableau, l'ensemble des k égaux à p dans le tableau. Si cet ensemble contient un seul élément, on l'enlève (reject) du tableau des produits:

produits=[]
(3..100).collect{|y| (2..y-1).collect{|x| if x+y<=100 then produits.push(x*y) end}}


polly=produits.reject{|p| (produits.select{|k| k==p}).size<2}
puts(polly.size)

Ah oui! Il y en a beaucoup qui restent!

Deuxième affirmation[modifier | modifier le wikicode]

Si Sam sait que Polly ne sait pas, c'est parce que quelle que soit la décomposition en somme d'entiers de celui qu'on lui a dicté, le produit correspondant est dans la liste précédente. Sinon elle eût pu savoir qui sont x et y, pour ce que Sam en sait! Sam ne va donc garder que les sommes n pour lesquelles la liste prod(n) calculée avec la fonction ci-dessus ne contient que des éléments de la liste polly, donc si leur intersection est égale à prod(n) (en effet dans l'algèbre de Boole des ensembles):

sam=(4..100).select{|s| prod(s)&polly==prod(s)}
puts(sam)

Il reste plutôt peu de sommes possibles:

11 17 23 27 29 35 37 41 47 53

On sait ce que Sam sait, mais Sam dit "Ça me suffit pas encore!", et nous aussi!

Troisième affirmation[modifier | modifier le wikicode]

Si Polly sait maintenant quels sont x et y, c'est que parmi les sommes ci-dessus, il n'y a pas que des doublons (produits communs à plusieurs sommes). Il y a un produit propre à une des sommes de Sam ci-dessus. Pour en savoir plus, Polly va constituer pour chacun des 10 nombres s ci-dessus, la liste de ses produits prod(s), puis chercher tous les nombres communs à au moins deux de ces 10 listes (les doublons). Ruby peut en faire de même, mais après avoir aplati le tableau des prod(s) (qui est un tableau à deux dimensions), et en plaçant dans la liste des doublons, tous les nombres qui apparaissent plus d'une fois dans le tableau des produits:

produits=sam.map{|s| prod(s)}
listeprods=produits.flatten

doublons=listeprods.select{|p| (listeprods.select{|k| k==p}).size>1}

Ensuite Polly enlève à chaque liste de produits des sommes de sam, les doublons (par une soustraction d'ensembles), et en comptant le nombre d'éléments de chacun des ensembles obtenus,

produits.each{|p| puts((p-doublons).size)}


Polly constate effectivement la présence d'un et un seul singleton:

11 17 23 27 29 35 37 41 47 53
3 1 3 9 9 10 7 13 13 18

Quatrième affirmation[modifier | modifier le wikicode]

Puisque Sam sait aussi sa somme, on sait que sa somme est 17 et le produit de Polly, 52:

puts(sam[1])
puts(prod(17)-doublons)

À la recherche des x et y perdus[modifier | modifier le wikicode]

Maintenant qu'on connaît la somme et le produit de x et y, il reste à déterminer ceux-ci, ce qui peut se faire en résolvant l'équation du second degré ou par une double boucle:

(3..100).collect{|y| (2..y-1).collect{|x| if x+y==17 and x*y==52 then puts(x,y) end}}

Les inconnues x et y sont maintenant connues!


Joukovski et Ruby

L'écoulement d'un fluide autour d'un cylindre est aisé à calculer (par exemple pour étudier l'effet Magnus), et toute transformation conforme permet d'en déduire l'écoulement autour du transformé du cercle (la trace du cylindre). En particulier lorsque le transformé a la forme d'une aile d'avion, ce qui est le cas avec la transformée de Joukovski. Ce calcul sera effectué par Ruby grâce à ses nombres complexes, puis affiché en fabriquant une figure svg. En fait:

  1. On utilise l'inverse de la transformée de Joukovski pour calculer l'écoulement autour du cercle unité.
  2. On agrandit légèrement ce cercle pour qu'il ait une transformée de Joukovski plus intéressante;
  3. On applique au nouveau cercle la transformée de Joukovski: On a l'écoulement autour de l'aile.

C'est la méthode utilisée par Nikolaï Joukovski au début du vingtième siècle pour les premières études sur l'aéronautique.

Du cercle au segment[modifier | modifier le wikicode]

Pour calculer l'écoulement autour du cercle unité, il suffit de trouver une transformation conforme qui transforme un segment (dont l'écoulement est trivial) en le cercle unité. Or l'image d'un point quelconque eit du cercle unité par la transformée de Joukovski est eit+e-it=2 cos(t): La transformation de Joukovski J(z) transforme le cercle unité en le segment [-2;2].

Et l'écoulement autour du segment est le plus simple possible, puisque c'est comme si le segment n'était pas là: Des droites verticales représentent les équipotentielles (ou isobares), et des droites verticales représentent les lignes de courant (ou écoulement) qui montrent la vitesse du vent. La transformation conforme qui produit cette grille est la transformation identique id(z)=z.

Du segment au cercle[modifier | modifier le wikicode]

Transformée inverse[modifier | modifier le wikicode]

Il suffit donc d'appliquer à cet écoulement l'inverse J-1 de la transformée de Joukovski, qui envoie le segment [-2;2] sur le cercle unité. Or J-1 est multiforme. On utilisera donc la fonction fplus valant sur la moitié droite de l'image, et la fonction fmoins valant sur la moitié gauche. Ces fonctions sont complexes:

require 'cmath'

def fplus(z)
	return (z+CMath.sqrt(z**2-4))/2
end
def fmoins(z)
	return (z-CMath.sqrt(z**2-4))/2
end
R=100

Le paramètre R est un facteur d'échelle permettant de zoomer sans avoir à refaire tout le script.

La figure est enregistrée dans un fichier au format svg appelé JoukovskiRuby1.svg:

figure=File.open("JoukovskiRuby1.svg","w")
figure.puts('<?xml version="1.0" encoding="utf-8"?>')
figure.puts('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"')
figure.puts('"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">')
figure.puts('<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480">')

La figure occupera donc 640 pixels de large et 480 pixels de haut, et s'appellera figure tout simplement.

Tracé des isobares[modifier | modifier le wikicode]

Côté droit de la figure[modifier | modifier le wikicode]

Pour tracer les isobares, on prend un point d'affixe x-2i et on le transforme par J-1. Puis on commence un path de svg par ce point. Ensuite on transforme des points situés sur la même droite verticale que le premier point choisi, et on les ajoute au path (une chaîne de caractères appelée ligne). La courbe obtenue (le path) sera en jaune et est inscrite à la fin dans le fichier:

#moitié droite des isobares
(0..25).collect { |i|
    z=fplus(Complex(i/10.0,-2.0))
	xa=z.real*R+320
	ya=240-z.imag*R
	ligne='<path d="M'
	ligne+=xa.to_s+' '+ya.to_s
	(-20..20).collect { |j|
		z=fplus(Complex(i/10.0,j/10.0))
		xa=z.real*R+320
		ya=240-z.imag*R
		ligne+=' L'+xa.to_s+' '+ya.to_s
		
	}
	ligne+='" stroke="yellow" stroke-width="1" fill="none"/>'
	figure.puts(ligne)
}

Côté gauche de la figure[modifier | modifier le wikicode]

Les isobares de gauche se dessinent de la même manière, en remplaçant fplus par fmoins:

#moitié gauche des isobares
(-25..0).collect { |i|
    z=fmoins(Complex(i/10.0,-2.0))
	xa=z.real*R+320
	ya=240-z.imag*R
	ligne='<path d="M'
	ligne+=xa.to_s+' '+ya.to_s
	(-20..20).collect { |j|
		z=fmoins(Complex(i/10.0,j/10.0))
		xa=z.real*R+320
		ya=240-z.imag*R
		ligne+=' L'+xa.to_s+' '+ya.to_s
		
	}
	ligne+='" stroke="yellow" stroke-width="1" fill="none"/>'
	figure.puts(ligne)
}

Tracé des lignes de courant[modifier | modifier le wikicode]

Pour les mettre en exergue, on fera les lignes de courant en rouge.

Côté gauche de la figure[modifier | modifier le wikicode]

Il suffit d'intervertir les rôles de i (abscisse) et j (ordonnée):

#moitié gauche de l'écoulement
(-25..25).collect { |j|
    z=fmoins(Complex(-3.0,j/10.0))
	xa=z.real*R+320
	ya=240-z.imag*R
	ligne='<path d="M'
	ligne+=xa.to_s+' '+ya.to_s
	(-30..0).collect { |i|
		z=fmoins(Complex(i/10.0-0.000001,j/10.0))
		xa=z.real*R+320
		ya=240-z.imag*R
		ligne+=' L'+xa.to_s+' '+ya.to_s
		
	}
	ligne+='" stroke="red" stroke-width="1" fill="none"/>'
	figure.puts(ligne)
}

Le décalage des abscisses permet d'éviter le passage par l'origine, où le changement de feuillet de la transformation donnerait des résultats graphiquement bizarres.

Côté droit de la figure[modifier | modifier le wikicode]

Même principe, en remplaçant fmoins par fplus:

#moitié droite de l'écoulement
(-25..25).collect { |j|
    z=fplus(Complex(0,j/10.0))
	xa=z.real*R+320
	ya=240-z.imag*R
	ligne='<path d="M'
	ligne+=xa.to_s+' '+ya.to_s
	(0..30).collect { |i|
		z=fplus(Complex(i/10.0,j/10.0))
		xa=z.real*R+320
		ya=240-z.imag*R
		ligne+=' L'+xa.to_s+' '+ya.to_s
		
	}
	ligne+='" stroke="red" stroke-width="1" fill="none"/>'
	figure.puts(ligne)
}

Tracé du cercle[modifier | modifier le wikicode]

Pour ajouter le cercle lui-même sur la figure, on peut utiliser l'objet circle de svg; on en profite pour fermer la bannière svg et clore le fichier:

figure.puts('<circle cx="320" cy="240" r="'+R.to_s+'" fill="white" stroke="black" stroke-width="1" />')
figure.puts('</svg>')
figure.close

C'est tout ce qu'il faut pour obtenir l'écoulement que voici:

Le défaut des isobares en-dessous du cercle (la figure aurait dû être symétrique par rapport à l'axe des abscisses) est lié au changement de feuillet de J-1. Pour y remédier, on peut remplacer la moitié du bas par la symétrique de la moitié du haut, ce qui double la longueur du script (une fonction par quadrant). La correction est laissée en exercice.

Conception de l'aile d'avion[modifier | modifier le wikicode]

Si on applique à la figure ci-dessus (avec le cercle unité), la transformation J(z), on obtient l'écoulement autour de [-2;2] qui est trivial. Mais on peut modifier le cercle pour qu'il passe toujours par le point d'affixe 1, et la transformée de Joukovski du nouveau cercle aura toujours un point de rebroussement d'affixe 2, mais peut avoir une forme de lemniscate, ou de profil d'aile d'avion, selon le paramètre choisi.

Tracé du profil[modifier | modifier le wikicode]

Une fonction affine complexe laisse le point d'affixe 1 invariant, si elle est de la forme fa(z)=P(z-1)+1. Le meilleur moyen de choisir la valeur du coefficient directeur P est d'implémenter fa et J avec un logiciel de géométrie dynamique (par exemple, CaRMetal qui exporte au format svg), et de bouger le point d'affixe P avec la souris jusqu'à ce que l'image du cercle par J(fa(z)) ait la forme voulue:

Le choix de P=0,93+0,15i paraît satisfaisant.

Calcul de l'écoulement[modifier | modifier le wikicode]

Pour la fonction de Joukovski, on ajoute un très petit nombre à z pour que le nombre zéro passe entre les mailles du filet, ce qui évite le risque d'avoir une erreur de division par zéro. À part ça, il suffit d'appliquer J(fa(z)) au cercle unité, et de remplacer le cercle final par son image par J(fa(z)) (pour dessiner l'aile). Ce qui donne le script suivant:

require 'cmath'

def fa(z)
	return Complex(0.93,-0.15)*(z-1)+1
end

def Joukovski(z)
	return z+1/(z+0.0000001)
end

def fplus(z)
	return Joukovski(fa((z+CMath.sqrt(z**2-4))/2))
end
def fmoins(z)
	return Joukovski(fa((z-CMath.sqrt(z**2-4))/2))
end
R=100


figure=File.open("JoukovskiRuby2.svg","w")
figure.puts('<?xml version="1.0" encoding="utf-8"?>')
figure.puts('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"')
figure.puts('"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">')
figure.puts('<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480">')
#moitié gauche de l'écoulement
(-25..25).collect { |j|
    z=fplus(Complex(-3.0,j/10.0))
	xa=z.real*R+320
	ya=240-z.imag*R
	ligne='<path d="M'
	ligne+=xa.to_s+' '+ya.to_s
	(-30..0).collect { |i|
		z=fplus(Complex(i/10.0-0.000001,j/10.0))
		xa=z.real*R+320
		ya=240-z.imag*R
		ligne+=' L'+xa.to_s+' '+ya.to_s
		
	}
	ligne+='" stroke="red" stroke-width="1" fill="none"/>'
	figure.puts(ligne)
}
#moitié droite de l'écoulement
(-25..25).collect { |j|
    z=fmoins(Complex(0,j/10.0))
	xa=z.real*R+320
	ya=240-z.imag*R
	ligne='<path d="M'
	ligne+=xa.to_s+' '+ya.to_s
	(0..30).collect { |i|
		z=fmoins(Complex(i/10.0,j/10.0))
		xa=z.real*R+320
		ya=240-z.imag*R
		ligne+=' L'+xa.to_s+' '+ya.to_s
		
	}
	ligne+='" stroke="red" stroke-width="1" fill="none"/>'
	figure.puts(ligne)
}

ligne='<path d="M'+(320+2*R).to_s+' 240'
(0..200).collect { |i|
	t=Math::PI*i/100
	z=Joukovski(fa(Complex(Math.cos(t),Math.sin(t))))
	xa=z.real*R+320
	ya=240-z.imag*R
	ligne+=' L'+xa.to_s+' '+ya.to_s
}
ligne+='" stroke="black" stroke-width="1" fill="white"/>'
figure.puts(ligne)

figure.puts('</svg>')
 
figure.close

Figure obtenue[modifier | modifier le wikicode]

L'exécution du script produit la figure suivante:

Le fait que les lignes de courant sont plus courbées sur le dessus que sur le dessous entraîne par la force de Bernoulli, une dépression sur le dessus de l'aide, qui est à l'origine de la portance: L'avion vole!

GFDL GFDL Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans texte de dernière page de couverture.