Mathématiques avec Python et Ruby/Quaternions et octonions en Python
Complexes
modifierOn a vu dans le chapitre précédent que pour Python, 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.
Pour toute la suite, il est conseillé d'importer les fonctions du module math de Python :
from math import *
Mais en réalité, seule la méthode hypot sera utilisée, ce qui signifie que le minimum nécessaire était
from math import hypot
Quaternions
modifierDéfinition
modifierToutes les méthodes (qui permettent de manipuler les quaternions) peuvent être regroupées dans une classe nommée Quaternion :
class Quaternion:
def __init__(self,a,b):
self.a=a
self.b=b
La première méthode, l'initialisation, crée donc deux variables a et b (qui seront des complexes, mais Python ne le sait pas encore) et les rendre accessibles par la notation avec un point, les nombres q.a et q.b (les deux complexes qui définissent le quaternion q) étant des propriétés du quaternion.
Affichage
modifierPour y voir quelque chose, une méthode d'affichage est nécessaire. Comme Python en possède déjà une (la conversion en chaîne de caractères, ou string, notée __str__), on va la surcharger :
def __str__(self):
aff='('
aff+=str(self.a.real)+')+('
aff+=str(self.a.imag)+')i+('
aff+=str(self.b.real)+')j+('
aff+=str(self.b.imag)+')k'
return aff
Un quaternion possède deux propriétés qui sont des nombres complexes, mais chacun d'eux est formé de deux nombres réels, donc un quaternion est formé de 4 nombres réels, ce sont ceux qui sont affichés par la méthode ci-dessus, lorsqu'on entre par exemple print(q).
Fonctions
modifierOpposé
modifier def __neg__(self):
return Quaternion(-self.a,-self.b)
En écrivant -q, on aura désormais l'opposé de q.
Module
modifier def __abs__(self):
return hypot(abs(self.a),abs(self.b))
Grâce à cette méthode, abs(q) retourne un réel, la racine carrée de sa norme.
Conjugué
modifier def conjugate(self):
return Quaternion(self.a.conjugate(),-self.b)
abs(q.conjugate()) renvoie le même résultat que abs(q) parce que tout quaternion a la même norme que son conjugué.
Opérations
modifierAddition
modifierPour additionner deux quaternions, on additionne leurs a respectifs, et leurs b respectifs :
def __add__(self,other):
return Quaternion(self.a+other.a,self.b+other.b)
Soustraction
modifier def __sub__(self,other):
return Quaternion(self.a-other.a,self.b-other.b)
Multiplication
modifierLe produit de deux quaternions est plus difficile à définir :
def __mul__(self,other):
c=self.a*other.a-self.b*other.b.conjugate()
d=self.a*other.b+self.b*other.a.conjugate()
return Quaternion(c,d)
Ce produit admet le quaternion Quaternion(1,0) comme élément neutre, et il est associatif comme tout produit qui se respecte. Mais il n'est pas commutatif :
p=Quaternion(-2+1J,2+3J)
q=Quaternion(3-2J,5+1J)
print(p*q)
print(q*p)
Division
modifierMultiplication par un réel
modifierPour définir le plus facilement possible le quotient de deux quaternions, on a intérêt à définir le produit d'un quaternion par un réel (on peut déjà l'effectuer avec la méthode de produit, en assimilant le réel r avec la quaternion Quaternion(r,0)). Mais comme le symbole de multiplication est déjà utilisé pour la multiplication des quaternions, il en faut un autre pour la multiplication d'un quaternion par un réel. Or il se trouve que __rmul__ (multiplication à l'envers) est encore disponible, donc pour peu qu'on multiplie à droite dans la définition, et à gauche en pratique, on peut ajouter cette méthode :
def __rmul__(self,k):
return Quaternion(self.a*k,self.b*k)
Alors, pour tripler un quaternion q, on peut faire, au choix, q*Quaternion(3,0), ou 3*q.
Division
modifierLe quotient de deux quaternions est désormais simple à définir :
def __div__(self,other):
return self*(1./abs(other)**2*other.conjugate())
Le quotient d'un quaternion par son conjugué est de norme 1 :
p=Quaternion(-2+1J,2+3J)
print(p/p.conjugate())
Cet exemple révèle que , ce qui revient à la décomposition suivante de 81 (un carré) comme somme de 4 carrés : .
Puissances
modifierMême si la multiplication des quaternions n'est pas commutative, elle est associative, c'est tout ce qu'il faut pour définir les puissances des quaternions à exposants entiers (et donc, par formules de Taylor, de la trigonométrie et des exponentielles de quaternions) :
def __pow__(self,n):
r=1
for i in range(n):
r=r*self
return r
Par exemple, on peut calculer le carré d'un quaternion :
q=Quaternion(2J/7,(3+6J)/7)
print(q**2)
Plus généralement, l'ensemble des solutions de est une sphère.
L'étude des itérés de où q et c sont des quaternions, mène à des ensembles de Mandelbrot quaternionniques ([1])
Résumé
modifierVoici le contenu complet de la classe Quaternion de Python :
from math import hypot
class Quaternion:
def __init__(self,a,b):
self.a=a
self.b=b
def __str__(self):
aff='('
aff+=str(self.a.real)+')+('
aff+=str(self.a.imag)+')i+('
aff+=str(self.b.real)+')j+('
aff+=str(self.b.imag)+')k'
return aff
def __neg__(self):
return Quaternion(-self.a,-self.b)
def __add__(self,other):
return Quaternion(self.a+other.a,self.b+other.b)
def __sub__(self,other):
return Quaternion(self.a-other.a,self.b-other.b)
def __mul__(self,other):
c=self.a*other.a-self.b*other.b.conjugate()
d=self.a*other.b+self.b*other.a.conjugate()
return Quaternion(c,d)
def __rmul__(self,k):
return Quaternion(self.a*k,self.b*k)
def __abs__(self):
return hypot(abs(self.a),abs(self.b))
def conjugate(self):
return Quaternion(self.a.conjugate(),-self.b)
def __div__(self,other):
return self*(1./abs(other)**2*other.conjugate())
def __pow__(self,n):
r=1
for i in range(n):
r=r*self
return r
Il suffit de placer tout ça dans un fichier appelé quaternions.py et disposer de toutes ces méthodes en important le module nouvellement créé par
from quaternions import *
Ce qui permet alors de déclarer des quaternions, puis d'effectuer des opérations dessus.
Octonions
modifierCe 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
modifierDéfinition
modifierfrom math import hypot
class Octonion:
def __init__(self,a,b):
self.a=a
self.b=b
Affichage
modifierComme est de dimension 8 sur , l'affichage est plus compliqué que celui des quaternions :
def __str__(self):
aff='('
aff+=str(self.a.a.real)+')+('
aff+=str(self.a.a.imag)+')i+('
aff+=str(self.a.b.real)+')j+('
aff+=str(self.a.b.imag)+')k+('
aff+=str(self.b.a.real)+')l+('
aff+=str(self.b.a.imag)+')li+('
aff+=str(self.b.b.real)+')lj+('
aff+=str(self.b.b.imag)+')lk'
return aff
On voit une arborescence apparaître, le a.a.real désignant la partie réelle du a du quaternion a de l'octonion. La notation avec les points prend ici tout son intérêt, permettant une concision pythonienne qui rendrait presque apprivoisés ces redoutables octonions!
Fonctions
modifierLes fonctions sur les octonions se définissent presque comme celles sur les quaternions, Cayley-Dickson oblige :
Opposé
modifier def __neg__(self):
return Octonion(-self.a,-self.b)
Module
modifier def __abs__(self):
return hypot(abs(self.a),abs(self.b))
C'est pour permettre cette concision qu'on a importé la méthode hypot du module math.
Conjugué
modifier def conjugate(self):
return Octonion(self.a.conjugate(),-self.b)
Opérations
modifierAddition
modifier def __add__(self,other):
return Octonion(self.a+other.a,self.b+other.b)
Encore une fois, le fait d'avoir surchargé la méthode __add__ de Python permet de noter simplement m+n la somme des octonions m et n.
Soustraction
modifier def __sub__(self,other):
return Octonion(self.a-other.a,self.b-other.b)
Multiplication
modifier def __mul__(self,other):
c=self.a*other.a-other.b*self.b.conjugate()
d=self.a.conjugate()*other.b+other.a*self.b
return Octonion(c,d)
Non seulement la multiplication des octonions n'est pas commutative, elle n'est plus associative non plus :
m=Octonion(Quaternion(3+4J,2-7J),Quaternion(1+3J,5-3J))
n=Octonion(Quaternion(2+1J,1-3J),Quaternion(2-2J,1+1J))
o=Octonion(Quaternion(3-2J,-5+3J),Quaternion(1-2J,2-1J))
print((m*n)*o)
print(m*(n*o))
Division
modifierGrâce à la notion de conjugué, on peut facilement définir le quotient de deux octonions, mais c'est encore plus facile en multipliant un octonion par un réel :
Produit par un réel
modifier def __rmul__(self,k):
return Octonion(k*self.a,k*self.b)
Quotient de deux octonions
modifier def __div__(self,other):
return self.conjugate()*(1./abs(other)**2*other)
Là encore, le quotient d'un octonion par son conjugué est de norme 1 :
m=Octonion(Quaternion(3+4J,2-7J),Quaternion(1+3J,5-3J))
n=Octonion(Quaternion(2+1J,1-3J),Quaternion(2-2J,1+1J))
print(m/m.conjugate())
print(abs(n/n.conjugate()))
Ces calculs permettent de décomposer un carré en somme de 8 carrés.
Puissances
modifierComme la multiplication des octonions n'est pas associative, les puissances des octonions ne présentent guère d'intérêt, sauf pour la puissance 2, et sur , l'ensemble des solutions de l'équation est une sphère de dimension 6.
Résumé
modifierLa classe Octonion de Python peut se résumer à ceci :
class Octonion:
def __init__(self,a,b):
self.a=a
self.b=b
def __str__(self):
aff='('
aff+=str(self.a.a.real)+')+('
aff+=str(self.a.a.imag)+')i+('
aff+=str(self.a.b.real)+')j+('
aff+=str(self.a.b.imag)+')k+('
aff+=str(self.b.a.real)+')l+('
aff+=str(self.b.a.imag)+')li+('
aff+=str(self.b.b.real)+')lj+('
aff+=str(self.b.b.imag)+')lk'
return aff
def __neg__(self):
return Octonion(-self.a,-self.b)
def __add__(self,other):
return Octonion(self.a+other.a,self.b+other.b)
def __sub__(self,other):
return Octonion(self.a-other.a,self.b-other.b)
def __mul__(self,other):
c=self.a*other.a-other.b*self.b.conjugate()
d=self.a.conjugate()*other.b+other.a*self.b
return Octonion(c,d)
def __rmul__(self,k):
return Octonion(k*self.a,k*self.b)
def __abs__(self):
return hypot(abs(self.a),abs(self.b))
def conjugate(self):
return Octonion(self.a.conjugate(),-self.b)
def __div__(self,other):
return self.conjugate()*(1./abs(other)**2*other)
Pour peu qu'on l'ait enregistrée dans un fichier octonions.py, il suffit pour pouvoir effectuer des calculs sur les octonions, d'importer ce fichier par
from octonions import *