Mathématiques avec Python et Ruby/Quaternions et octonions en Python

Complexes

modifier

On 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

modifier

Définition

modifier

Toutes 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

modifier

Pour 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

modifier

Opposé

modifier
	def __neg__(self):
		return Quaternion(-self.a,-self.b)

En écrivant -q, on aura désormais l'opposé de q.

	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

modifier

Addition

modifier

Pour 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

modifier

Le 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

modifier
Multiplication par un réel
modifier

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

Le 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

modifier

Mê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  q et c sont des quaternions, mène à des ensembles de Mandelbrot quaternionniques ([1])

Résumé

modifier

Voici 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

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
from math import hypot

class Octonion:
	
	def __init__(self,a,b):
		self.a=a
		self.b=b

Affichage

modifier

Comme   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

modifier

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

modifier

Addition

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

modifier

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

modifier

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

modifier

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

Bibliographie

modifier
  • De par leur utilité en infographie 3D, les quaternions sont utilisés dans Blender, avec cette description : [2]
  • Sur les octonions, le livre de John Baez est une lecture hautement conseillée : [3]