Mathématiques avec Python et Ruby/Version imprimable2

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

Avec une chaîne de caractères

modifier

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

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

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

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

Addition, soustraction et multiplication

modifier

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

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

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.

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

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

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

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

Itérateur

modifier

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

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

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

Avec un indice

modifier

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

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

à nombre prédéterminé d'exécutions

modifier

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

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

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

Numérateur

modifier

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

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


Dénominateur

modifier

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

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


Valeur approchée

modifier

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

Opérations unaires

modifier

Opposé

modifier

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

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

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


Addition

modifier

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

La différence de deux fractions est une fraction :

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


Multiplication

modifier

Le produit de deux fractions est une fraction :

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


Division

modifier

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

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

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

Réduite de Farey

modifier

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

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

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

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

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

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

Opérations

modifier

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

Logarithmes

modifier

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

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

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

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

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

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

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

Exponentielles

modifier

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

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

directes

modifier

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

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

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

Definition et affichage

modifier

Définition

modifier

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
	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
	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

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

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

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


Conjugué

modifier

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

Addition

modifier

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

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

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

Multiplication

modifier

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

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

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

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

Définition

modifier

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

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

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

Comme pour les quaternions:

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

Conjugué

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

Opérations

modifier

Addition

modifier

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
	def -(o)
		Octonion.new(@a-o.a,@b-o.b)
	end

Multiplication

modifier
	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
	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

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
  • 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

Évènements certain et impossible

modifier

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
 

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
 

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

Notation

modifier

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

Avec le dé

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

Avec les cartes

modifier

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

Notation

modifier

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
petit=[1,2,3,4]
pair=[2,4,6]
puts(petit|pair)

Avec les cartes

modifier

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

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

Avec le dé

modifier
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
puts(univers-figure)
puts(univers-pique)

Probabilités

modifier

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
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
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

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

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

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

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

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

Tirer une carte au hasard

modifier

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

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

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

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

Par fonction

modifier

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

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

Suite logistique

modifier

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

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

Suites arithmétiques

modifier

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

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

Suite de Fibonacci

modifier

Calcul des termes

modifier

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

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

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

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

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

Premier exemple

modifier

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

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

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

Méthode de Heron

modifier

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
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

Sujet Bac STG CGRH Métropole-Réunion Septembre 2007

modifier
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

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

Suite de l'énoncé du Bac STG 2007

modifier

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

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

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

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

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

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.

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

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

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')}

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

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

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

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

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

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.

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

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

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

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

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

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

    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

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

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

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

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

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

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))

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

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

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
    def x
        @x
    end

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

Ordonnée

modifier
    def y
        @y
    end

Affichage

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

Il suffit pour afficher un vecteur u, de faire

puts(u)

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

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:

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

Il y a deux multiplications intéressantes:

Par un nombre

modifier

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

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.

De colinéarité

modifier

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

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

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

Exemple

modifier

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

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

Vecteur directeur

modifier
    def directeur
        @a.vecteur(@b)
    end

On obtient le vecteur directeur de d par d.directeur

Alignement

modifier

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

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

Équation cartésienne

modifier
    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

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

require 'mathn'


Coefficient directeur

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

Ordonnée à l'origine

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

Équation

modifier

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

Parallélisme

modifier

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

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

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

Exemple

modifier

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

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

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

Sujet 2005, Exercice 2

modifier
É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

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)

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!

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.

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...

On continue l'épuration:

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

puts(solutions)

Plus que deux candidats possibles !

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

puts(solutions)

Pas de changement, toujours deux candidats.

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
 
É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
solutions=(40..200).to_a

puts(solutions.size)


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

puts(solutions.size)

Plus que 27 nombres à tester.

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

puts(solutions)

Plus que 5 nombres à tester.

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

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

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

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

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

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

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

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

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

Transformée inverse

modifier

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

Côté droit de la figure

modifier

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

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

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

Côté gauche de la figure

modifier

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

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

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

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

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

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

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 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.