
Pour corriger un code QR avec erreur ou rature, on a besoin de savoir manipuler un corps fini prĂ©cis, đœ256, encore faut-il savoir de quoi on parle.
En vue de corriger les codes QR dĂ©corĂ©s ou abĂźmĂ©s (voir articles prĂ©cĂ©dents [1]), on a besoin de notions mathĂ©matiques plus poussĂ©es. Cet article va prĂ©senter la notion mathĂ©matique de corps fini, sur laquelle le code correcteur de Reed-Solomon est construit. On construira en Python le corps â€/p†oĂč p est un nombre premier. Les codes sont disponibles sur GitHub [2].
Cet article parlera de mathĂ©matiques, contrairement aux autres. On prĂ©sentera ce quâest, de maniĂšre gĂ©nĂ©rale, un corps puis nous construirons un corps fini premier pour, dans lâarticle suivant, obtenir le corps dont nous avons besoin et les structures algĂ©briques utiles Ă lâaide de briques orientĂ©es objet.
1. Un peu de mathématiques
Nous commençons par dĂ©finir ce quâest un corps avant de nous intĂ©resser Ă une construction possible.
1.1. Quâest-ce quâun corpsâŻ?
En gros, un corps est un ensemble de « nombres » dans lequel on peut effectuer les quatre opĂ©rations sans se demander sâil nây a pas un loup (dans Paris) ou une impossibilitĂ© (Ă part, bien sĂ»r, diviser par 0).
Formellement, on se donne un ensemble K sur lequel on a deux opĂ©rations notĂ©es habituellement + et Ă.
Lâaddition a les propriĂ©tĂ©s suivantes :
- Elle est commutative : a+b=b+a pour tous a et b de K ;
- Elle est associative : (a+b)+c=a+(b+c) quels que soient a, b, câK ce qui veut dire en pratique quâon nâest pas tenu dâutiliser ni dâĂ©crire des parenthĂšses ;
- La loi possĂšde un Ă©lĂ©ment neutre 0 pour lequel âaâK, a+0=0+a=a ;
- Tout Ă©lĂ©ment a a un « inverse » b quâon appelle opposĂ©, câest-Ă -dire que a+b=b+a=0. On note b=âa et aâb=a+(âb).
Ceci veut dire que (K,+) est un groupe commutatif.
(K*,Ă) est aussi un groupe, non nĂ©cessairement commutatif (K* signifie K sans 0). Lâinverse de aâ 0 est notĂ© 1/a ou aâ1 puisque aâ1Ăa=aĂaâ1=1.
Enfin, la multiplication est distributive par rapport Ă lâaddition, ce qui veut dire que âa, b, câK, (a+b)Ăc=aĂc+bĂc et de mĂȘme Ă gauche. Par convention, la multiplication est prioritaire sur lâaddition, ce qui veut dire que a+bĂc=a+(bĂc).
On a tous dĂ©jĂ calculĂ© dans des corps comme â (lâensemble des nombres rĂ©els) ou â (les nombres complexes utilisĂ©s par exemple en Ă©lectronique analogique). Ces deux derniers corps contiennent tous les deux le corps â des fractions des entiers relatifs, câest mĂȘme le plus petit sous-corps quâils contiennent (on appelle ce sous-corps leur sous-corps premier). Ces trois corps sont infinis.
En revanche, lâensemble †des entiers relatifs nâest pas un corps car seuls 1 et â1 ont des inverses dans †pour la multiplication (qui sont eux-mĂȘmes), †est cependant un anneau muni dâune division euclidienne.
Les flottants utilisĂ©s en informatique munis des opĂ©rations + et Ă ne forment pas un corps, mĂȘme pas un anneau, rien en fait. Il faut le savoir quand on fait du calcul scientifique Ă cause de mantisses et dâexposants de taille limitĂ©e. Ainsi, 10100+10â100â10100=⊠eh bien ça vaut 0 pour une machine qui ne fait pas de calcul formel. Câest un pont aux Ăąnes de prof de maths qui nâempĂȘche pas de travailler avec en prenant quelques prĂ©cautions. Par exemple, pour une calculette de poche couramment rencontrĂ©e en classe, 10n+10ânâ10n vaut 0 dĂšs que nâ©Ÿ7. [3]
1.2 Premiers corps finis
Ăvidemment, un corps fini ne contient pas â, ilest construit autrement.
On commence par choisir un nombre premier p, le reste de la division dâun nombre entier relatif par p est donc entre 0 et pâ1. VoilĂ notre premier corps fini : {0,1,âŠ,pâ1} muni de lâaddition et de la multiplication modulo p quâon note â€/p†ou â€p, parfois â€/p. Les corps finis ont tous pour sous-corps premier un corps fini du type â€/p†avec p premier (p est sa caractĂ©ristique).Pour distinguer les Ă©lĂ©ments de â€/p†des nombres entiers classiques, on les Ă©crira surmontĂ©s dâune barre.
Ainsi, modulo 7 : car 11=1Ă7+4 et
.
Attention, si n nâest pas un nombre premier, â€/n†nâest pas un corps. Par exemple, si n=4, nâa pas dâinverse puisque
.
est un diviseur de
. Dans ce cas, les seuls nombres q qui ont un inverse sont ceux qui nâont pas de facteur premier commun avec n, en dâautres mots, si PGCD(n,q)=1. On dit que â€/n†est un anneau (oĂč Ă part lâabsence Ă©ventuelle dâinverse pour la multiplication, la structure algĂ©brique est la mĂȘme, et en plus sans division euclidienne).
Quâen est-il de la division, par exemple par âŻ?
Si le corps est petit, on peut chercher lâinverse Ă la mainautrement dit par force brute : . Donc diviser par
revient Ă multiplier par son inverse qui est
. Lâalgorithme efficace utilisĂ© dans notre cas sera lâalgorithme dâEuclide Ă©tendu qui donne les coefficients de Bezout u et v dans lâĂ©quation 4Ău+7Ăv=1 (oĂč u et v sont des entiers relatifs). En fait, seul u nous est utile, on lâobtient facilement Ă la main par la mĂ©thode de Blankinship (ou pivot de GauĂ) [4] oĂč on effectue des opĂ©rations Ă©lĂ©mentaires (combinaisons linĂ©aires ou permutations) sur les lignes de la matrice suivante :
La premiĂšre colonne contient un seul 1 et le reste est constituĂ© de 0, on a terminĂ© lâalgorithme : u=2, v=â1 et 7Ă(â1)+2Ă4=1 donc lâinverse de est bien
dans â€/7â€.
Cette mĂ©thode fonctionne aussi quand on veut calculer le PGCD de plusieurs nombres etleurs coefficients de Bezout.Ainsi, si la matrice Ă chaque Ă©tape est Mi,j, pour tout iâ{1;2}, Mi,1=7ĂMi,2+4ĂMi,3 pendant tout le dĂ©roulement de lâalgorithme.
Voici la fonction qui retourne lâinverse de a modulo n, elle se trouve dans qrcodeoutils.py :
01: def bezout(n,a):
02: r,u,v,rr,uu,vv=a,1,0,n,0,1
03: while rr!=0:
04: q=r//rr
05: r,u,v,rr,uu,vv=rr,uu,vv,r-q*rr,u-q*uu,v-q*vv
06: return u%n,v%n
Il sâagit ici de lâalgorithme classique quâon peut allĂ©ger des variables v et vv qui nâont aucune utilitĂ© dans la suite. On nâa pas vĂ©rifiĂ© si le PGCD Ă©tait bien Ă©gal Ă 1, ce qui est immĂ©diat dans un corps, on nâa pas non plus vĂ©rifiĂ© si a==0.
Le corps fini utilisĂ© dans les codes QR nâa pas cette structure, trop simple, quâon verra dans la partie suivante : un corps fini est un espace vectoriel de dimension finie sur un corps fini premier, tout comme â est un espace vectoriel de dimension 2 sur â (qui nâest cependant pas un corps premier). Voyons comment construire un corps â€/p†en crĂ©ant une classe munie de toutes les mĂ©thodes adĂ©quates.
2. Construction effective de â€/pâ€
On va construire pour commencer lâanneau â€/p†dans le cas oĂč p est un nombre premier, câest-Ă -dire le corps â€/pâ€, en Python.
2.1 MĂ©morisation
Mon second code, naĂŻf, utilise une fonction qui retourne une classe Zn qui dĂ©pend du paramĂštre p (la classe est donc paramĂ©trĂ©e). La construction fonctionne Ă peu prĂšs mais coince quand il faut commencer Ă vĂ©rifier les types dans les classes emboĂźtĂ©es. Jâai donc utilisĂ© le code de Jeremy Kun [5] qui ressemble au mien [2], aux dĂ©corateurs prĂȘts.
Un dĂ©corateur permet de modifier une fonction sans utiliser une batterie de tests (regardez mon deuxiĂšme code oĂč je passe beaucoup de temps Ă vĂ©rifier finement les types). Le code de Jeremy [6] est certes puissant mais jâai dĂ» le modifier ponctuellement, notamment pour lâopĂ©ration puissance ou la division euclidienne (sans intĂ©rĂȘt dans un corps). Le voici en version francisĂ©e, dans qrcodeoutils.py :
def memorise(f):
cache=dict()
def fonctionmemorisee(*args):
if args not in cache:
cache[args]=f(*args)
return cache[args]
fonctionmemorisee.cache=cache
return fonctionmemorisee
if __name__=="__main__":
@memorise
def f(x):
print(x,x*x,"premiĂšre passe")
return x*x
print(f(2))
print(f(3))
print(f(2))
print(f.cache)
print(f)
Voici la sortie de ce code :
> ./memorisation.py
2 4 premiĂšre passe
4
3 9 premiĂšre passe
9
4
{(2,): 4, (3,): 9}
<function memorise.<locals>.fonctionmemorisee at 0xb729c6ec>
On teste si la valeur est mĂ©morisĂ©e. Sinon, on la stocke dans le cache qui est un simple dictionnaire. Ăvidemment, ce code stocke absolument tout sans contrĂŽle. Dans un code qui tourne indĂ©finiment, il faudrait au minimum contrĂŽler la taille du cache.
2.2 Construction de la classe modulo p
La fonction defzn dĂ©finit une classe qui dĂ©pend du paramĂštre p qui est un nombre entier positif. Elle est mĂ©morisĂ©e donc deux classes de mĂȘmes paramĂštres p sont en fait identiques et non deux classes distinctes qui font nĂ©anmoins la mĂȘme chose.
01: @memorise
02: def defzn(p):
03: class Zn():
04: def __init__(self,n):
05: self.n=n%p
06: self.corps=Zn
Le constructeur dâun Ă©lĂ©ment de la classe le dĂ©finit par ses propriĂ©tĂ©s : sa valeur et le corps auquel il appartient.
08: def __add__(self,m):
09: return Zn(self.n+m.n)
10: def __sub__(self,m):
11: return Zn(self.n-m.n)
12: def __mul__(self,m):
13: return Zn(self.n*m.n)
On dĂ©finit lâaddition, la soustraction et la multiplication dans â€/p†qui permettent dâutiliser les opĂ©rateurs binaires +, â et *.
14: def __truediv__(self,m):
15: return self*m._inverse()
La division (/ en Python 3 et non // qui est la division euclidienne que nous utiliserons plus tard mais pas dans un corps, et qui est dĂ©finie par la mĂ©thode __floordiv__) nĂ©cessite lâinverse modulo p puisque diviser par un nombre est Ă©quivalent Ă multiplier par son inverse (comme on lâapprend au collĂšge) soit , âaââ€/pâ€, âbââ€/pâ€*, la fonction _inverse est dĂ©finie plus loin.
16: def __neg__(self):
17: return Zn(-self.n)
18: def __eq__(self,m):
19: return isinstance(m,Zn) and self.n==m.n
La nĂ©gation unaire (autrement dit lâopposĂ©) est dĂ©finie ainsi que le test dâĂ©galitĂ©. Remarquez que les comparaisons < ou â©Ÿ nâont aucun intĂ©rĂȘt.
20: def __pow__(self,exp):
21: if exp>0:
22: prod=1
23: x=self.n
24: while exp>1:
25: if exp%2:
26: prod*=x
27: x*=x
28: x%=p
29: exp//=2
30: return Zn(prod*x)
31: elif exp==0:
32: return Zn(1)
33: else:
34: return self._inverse()**-exp
Il sâagit de lâopĂ©rateur de puissance ** y compris si lâexposant est nĂ©gatif. Dans ce cas, on calcule la puissance positive de lâinverse. Lâexponentiation rapide revient Ă dĂ©composer lâexposant en base 2, câest lâidĂ©e de la multiplication pratiquĂ©e en Ăgypte antique et encore aujourdâhui en Russie.
Par exemple si on doit calculer a45=a1a4a8a32, la suite des divisions de 45 par les puissances de 2 donne :
divisions |
45=2Ă22+1 |
22=11Ă2+0 |
11=5Ă2+1 |
5=2Ă2+1 |
2=2Ă1+0 |
1=1 |
reste |
1 |
0 |
1 |
1 |
0 |
1 |
prod |
a1 |
a1 |
a1a4=a5 |
a5a8=a13 |
a13 |
a13a32=a45 |
x |
a1 |
a2 |
a4 |
a8 |
a16 |
a32 |
35: def __str__(self):
36: return str(self.n)+"\u0305"
37: def __repr__(self):
38: return "%d [mod %d]"%(self.n,self.p)
La premiĂšre fonction est utilisĂ©e par les fonctions internes str et print, la deuxiĂšme quand on affiche une liste dâĂ©lĂ©ments de la classe, voir ci-aprĂšs.
39: def _inverse(self):
40: i,_=bezout(p,self.n)
41: return Zn(i)
Dans la fonction qui calcule lâinverse, on ne vĂ©rifie pas si lâĂ©lĂ©ment est bien inversible, comme on travaille dans un corps fini, on suppose que tout va bien. Au cas oĂč on diviserait quand mĂȘme par 0, une exception sera levĂ©e dans la boucle dans la fonction bezout lors de la division euclidienne (ligne 04 du code source de bezout, voir plus haut).
42: Zn.p=p
43: Zn.__name__ ="â€/%dâ€"%p
44: return Zn
Enfin, on dote la classe elle-mĂȘme des deux propriĂ©tĂ©s : son cardinal et son nom et on la retourne.
Jouons un peu avec notre classe :
if __name__=="__main__":
mod7=defzn(7)
print([mod7(i) for i in range(10)])
print(mod7(3)**-2)
print(mod7(2)*mod7(4)-mod7(5))
print(mod7(1).corps.__name__)
print(mod7.__name__)
z7=defzn(7)
print(z7==mod7)
On obtient ceci :
[0 [mod 7], 1 [mod 7], 2 [mod 7], 3 [mod 7], 4 [mod 7], 5 [mod 7], 6 [mod 7], 0 [mod 7], 1 [mod 7], 2 [mod 7]]
4Ì
3Ì
â€/7â€
â€/7â€
True
Conclusion
Voici ce quâon peut faire en Python. Le problĂšme est quâil faut empiler beaucoup de briques par-dessus cette structure alors quâen fait, on peut accĂ©lĂ©rer le processus. Ce qu'il ne faut pas faire pour corriger un code QR... et ce n'est pas fini !
Références
[1] PATROIS N., « Décoder un code QR », GNU/Linux Magazine n°194, p. 32 à 41.
[2] Le code et les exemples sont présents ici : https://github.com/GLMF/GLMF194.
[3] LANGROGNET F., « Peut-on vraiment calculer avec un ordinateur : les opérations », GNU/Linux Magazine n°194, p. 22 à 31.
[4] Lâalgorithme de Blankinship est prĂ©sentĂ© ici : http://www.les-mathematiques.net/phorum/read.php?5,516246.
[5] http://jeremykun.com/2014/03/13/programming-with-finite-fields/. Jâai francisĂ© le code et je lui ai ajoutĂ© et supprimĂ© ce qui me semble ou non pertinent mathĂ©matiquement (par exemple, la division euclidienne ou la valeur absolue nâont aucun intĂ©rĂȘt dans un corps fini).
[6] http://jeremykun.com/2012/03/22/caching-and-memoization/.