TP d'Algebre pour 1A Informatique
TP d'Algebre pour 1A Informatique
1A informatique
Consignes pour le code source : voici quelques règles attendues pour le code
source à rendre. Si certaines sont évidentes, d’autres peuvent être adaptées en
fonction du rendu ou des consignes demandées pendant les TP :
1. Le programme doit compiler sous Linux (version des machines de TP par
défaut) sans erreurs. En particulier, il ne doit pas y avoir d’erreurs dues à
un mélange entre tabulations et espaces. Si une fonction cause une erreur
à la compilation, on mettra son appel en commentaire.
2. Lors de l’exécution, le programme doit afficher tout ce qui est demandé
de manière lisible. Par exemple, question 1 : ..., question 2 : ... ou encore
multiplication des entiers (exercice 1) : ..., multiplication dans un corps
(exercice 2) : ..., etc ...
3. Lors de l’exécution, le programme ne doit pas afficher des valeurs in-
termédiaires utilisées pour le debug. Une fois que le programme fonctionne,
enlever du code source tout ce qui a été utilisé pour le debug (notamment
les print, même laissé en commentaire).
4. Le code source ne doit pas contenir des fonctions, bout de fonctions ou
bout de code commentés qui ne servent à rien dans le programme. De
même on évitera les variables inutilisées ainsi que les instructions ou les
fonctions inutiles.
5. Le code source doit être commenté. Au minimum une phrase pour chaque
fonction (pas nécessairement les fonctions tests) mais aussi à chaque fois
que c’est utile pour la compréhension du code (théorèmes utilisés ou choix
d’implémentation par exemple).
6. Le code source doit contenir l’ensemble des fonctions demandées, puis une
partie main (voir ci-dessous) qui contient l’appel aux fonctions tests (qui
appellent chacune la ou les fonctions testées). On évitera d’appeler les
fonctions au millieu du code, à l’aide de print commentés ou non.
1
Partie principale : contrairement au C, il n’y a pas de fonction main en
python. Il existe n’éanmoins une variable spéciale __name__ qui prend la va-
leur __main__ si le fichier source est exécuté comme programme principal (cela
permet de ne pas exécuter une partie du code si le fichier est appelé avec la
commande import décrite ci-dessous mais aussi de rendre le code plus lisible).
2
TP1 : Arithmétique
Exercice 1 : écrire une fonction pgcd(x, y) qui prend deux entiers x et y en
entrée et retourne le pgcd(x, y) en utilisant l’algorithme d’Euclide. La complexité
de cet algorithme est polynomiale en O(m3 ) où m est la taille binaire de x et y.
Ecrire une fonction Test_pgcd() qui vérifie que pgcd(2, 3) = pgcd(1, -2) = 1
et pgcd(2, 0) = pgcd(-2, 4) = 2.
Ecrire une fonction test_Bezout() qui vérifie que Bezout(11, -7) retourne 1,
2, 3 et que Bezout(14, -21) retourne 7, −1, −1.
3
Pourquoi ça marche ? On note ri le reste de la division euclidienne calculée
au temps i. Alors il existe ui et vi tel que ri = xui + yvi . Pour que cette relation
soit vérifiée au temps 1, il faut que u0 = 1, v0 = 0, u1 = 0 et v1 = 1.
L’algorithme d’Euclide vérifiant les relations ri−1 = ri qi + ri+1 , on obtient
ui+1 = ui−1 − ui qi et vi+1 = vi−1 − vi qi . Soit rN −1 = pgcd(x, y) le dernier reste
non nul. Alors u = uN −1 et v = vN −1 .
est 5809605995369958062859502533304574370686975176362895236661486152287
2037309971102257373360445331184072513261577549805174439905295945400471
2166288567218703240103211163970644049884404985098905162720024476580704
1812394729680540024104827976584369381522292361208779044769892743225751
7380769795688113095791255113330932435195537848163063815801618602002474
9256844815024251530444957718760413642873858099017255157393414625583036
6405915000869643732053218566832545291107903722831634138599586406690325
9597251874471690595408050123102096390117507487600170953607342349457574
4
1627299485601330861695852995830467763701918159408852834506092632527129
9300275749022508481074607588020050657868884707522177700826705479749249
2684712072741722235492913089389585055523678859798090825458374599833558
3201134735593118276442552243746028302162288807114157479906331936740935
1465195552974198335677586764686451898341783871926484951037982181747437
201803318881419268
Écrire une fonction test_primalité_liste qui prend une liste d’entiers en
argument et retourne True si les éléments de la liste sont deux à deux premiers
entre eux et False sinon (utiliser la fonction pgcd). Appeler cette fonction en
début de la fonction précédente.
5
TP2 : Primalité
Exercice 1 : Soit p 6= 2 un nombre premier de la forme p = 2k q + 1, avec
q impair, et a ∈ {1, . . . , p − 1}. Alors ap−1 ≡ 1 mod p (théorème de Fermat).
2k
De plus on vérifie facilement 1 que : ap−1 − 1 = aq − 1 = (aq − 1)(aq +
k−1
1)(a2q + 1) . . . (a(2 )q + 1). Alors, soit aq ≡ 1 mod p, soit il existe un entier
i
i ∈ {0, . . . , k − 1} tel que a2 q ≡ −1 mod p (l’équation X 2 ≡ 1 mod p possède
exactement deux solutions, 1 et -1, si et seulement si p est premier).
Le test de primalité de Miller-Rabin utilise cette proposition. Pour tester la
primalité d’un entier p = 2k q + 1, on génère aléatoirement un entier a (appelé
i
témoin) et si aq 6= 1 mod p et s’il n’existe pas d’entier i tel que a2 q ≡ −1 mod p,
alors p n’est pas un nombre premier. La réciproque n’est pas vraie et on peut
montrer que la probabilité d’erreur est inférieure à 1/4.
Ecrire une fonction temoin_Miller_Rabin(a, p) qui prend en entrée un entier
p dont on veut tester la primalité et un entier a qui retourne False si p n’est
pas premier et True si p est premier (avec une probabilité d’erreur 1/4) à l’aide
de l’algorithme suivant (lire aussi les remarques qui suivent l’algorithme) :
6
Bien entendu, une probabilité d’erreur de 1/4 si le test retourne True n’est pas
satisfaisante. L’idée est donc de générer t entiers aléatoires (utiliser la fonction
randint() de la librairie random), et si le test précédent renvoit True à chaque
fois, on peut montrer que l’on obtient une probabilité d’erreur ≤ 1/4t (la pro-
babilité d’erreur est donc aussi petite que l’on veut). Dans ce TP on utilisera
une valeur de t = 40, pour avoir une probabilité d’erreur ≤ 2−80 .
Ecrire une fonction Miller_Rabin(p,t) qui prend en entrée un entier p dont
on veut tester la primalité et un entier t et qui retourne False si p n’est pas
premier et True si p est premier, avec une probabilité d’erreur 1/4t .
Ecrire une fonction test_Miller_Rabin() qui teste la fonction précédente avec
deux entiers premiers et non premiers de votre choix.
7
TP3 : Factorisation
Nous allons étudier le nombre d’itérations théoriques pour les deux fonctions.
On suppose que p > q pour être cohérent avec les notations précédentes. Vérifier
que
√ le nombre d’itérations pour la fonction factorisation_naive(N) est p -
d N e. Pour la fonction Fermat(N), on remarque que N = pq = ( p+q ) 2
−( p−q 2
2 ) .
p+q
√ 2
2
On a donc réalisé 2 −d N e itérations (en effet le nombre x tel que x −N soit
√
un carré est p+q
2 et on a initialisé x à d N e). Vérifier que le nombre d’itérations
retournées par les deux fonctions correspondent bien à ce qui était attendu (à
placer en fin de la fonction qui teste la fonction Fermat).
8
Ecrire une fonction testComplexite(n) qui génère deux nombres premiers
aléatoires p et q de n bits (utiliser n = 16). Pour générer ces entiers, utili-
ser au choix les fonctions du TP précédent, la fonction getPrime(n) du module
Cryptodome.Util.number ou randprime(1 << n - 1, (1 << n) - 1) du mo-
dule sympy. Appeler les deux fonctions précédentes avec N = pq et vérifier que
le nombre d’itérations obtenu correspond bien à la théorie.
1. t, x, y ← 0, a, a
2. Répéter :
(a) t ← t + 1
(b) x ← f (x)
(c) y ← f (f (y))
(d) Si x = y retourner t
√
Le nombre moyen de tours de boucle dans cet algorithme est en O( N ), du au
paradoxe des anniversaires (voir cours de cryptographie en seconde année).
9
TP4 : Fractions continues
avec a1 = b b10 c et b1 = 1
b0 − a1 pour obtenir x = a0 + 1
a1 +b1 , puis continuer ...
1
Au rang n, on obtient x = a0 + .
1
a1 +
.. 1
.+
an + bn
b0 1 1 1
x = a0 + = a0 + q = a0 + b1
= a0 + 1 = ...
q b0 a1 + b0
a1 + b0
b1
10
Exercice 2 (fraction continue d’un irrationnel) : écrire une fonction
Reduites(x, N) qui retourne la réduite√ d’ordre N d’un irrationnel x, à l’aide
de l’algorithme suivant (à tester avec 2), où bxc peut se calculer avec floor :
1. u0 , u1 , v0 , v1 ← 1, bxc, 0, 1
2. a0 , b0 ← bxc, x − bxc
3. Pour i = 2, . . . N
(a) ai−1 ← b1/bi−2 c
(b) bi−1 ← 1/bi−2 − ai−1
(c) ui ← ai−1 ∗ ui−1 + ui−2
(d) vi ← ai−1 ∗ vi−1 + vi−2
4. Retourner [u1 /v1 , u2 /v2 , . . . un /vn ]
√
Si on applique l’algorithme sur 2 avec N = 10, on obtient un = [1, 3, 7, 17,
41, 99, 239, 577, 1393, 3363] vn = [1, 2, 5, 12, 29, 70, 169, 408, 985, 2378] an =
[1, √
2, 2, 2, 2, 2, 2, 2, 2, 2] La sortie donne les 10 premières réduites (arrondies)
de 2 : [1, 1.5, 1.4, 1.4167, 1.4138, 1.4143, 1.4142, 1.4142, 1.4142, 1.4142].
Vous pouvez aussi regarder les réduites √ de π (from math import pi) ou les
quotients partiels du nombre d’or 1 + 5/2.
11
On a un problème d’arithmétique flottante, du à une approximation des cal-
culs qui se propage. Pour résoudre ce problème, nous allons utiliser le module
decimal en rajoutant : from decimal import Decimal. Comparer la précision
des nombres sqrt(61) et Decimal(61).sqrt().
Remarque : on peut modifier la précision (ici à 50) avec getcontext().prec = 50
en rajoutant from decimal import getcontext.
Modifier la fonction equation_Pell_Fermat(A) de l’exercice précédent, en cal-
culant les réduites de Decimal(61).sqrt() au lieu de sqrt(61) pour obtenir
la solution à l’équation X 2 − 61Y 2 = 1.
12
TP5 : Logarithme discret
Soit (G, ×) un groupe cyclique multiplicatif d’ordre n et de générateur g. Alors
on a l’isomorphisme de groupe f : (Zn , +) → (G, ×) défini par f (k) = g k car
pour tout h ∈ G, il existe un unique entier k compris entre 0 et n − 1 tel que
h = g k . Le calcul de l’image réciproque par f d’un élément de G est appelé
problème du logarithme discret. C’est un problème algorithmique difficile dans
le sens où si n est un tres grand entier on ne pourra pas le résoudre.
Dans ce TP on considère le groupe multiplicatif G = Z× p , où p est un nombre
premier qui est d’ordre n = p − 1. Si ce groupe n’a pas encore été vu en cours,
on admet qu’il soit cyclique. Ecrire une fonction loi(g1, g2) qui retourne
g1 g2 mod p, où p est défini comme une variable globale.
13
Entrées : g et h vérifiant h = g k .
Sortie : l’entier k correspondant.
1. x ← e et i ← 0.
2. Tant que x 6= h :
(a) x ← xg
(b) i ← i + 1.
3. Retourner i.
Ecrire une fonction testDL qui appelle la fonction précédente avec les même
données que testExp.
Entrées : n, g et h vérifiant h = g k .
Sortie : l’entier k correspondant.
√
1. w ← d ne, la longueur du pas de géant (celle du pas de bébé étant
1).
2. Calculer et stocker pour i de 0 à w les valeurs g iw .
3. Calculer à partir de j = 0 les valeurs hg −j jusqu’à trouver un élément
hg −j = g iw , présent dans la première liste.
4. Sortir l’entier (wi + j) mod N où i et j sont les indices trouvés au
pas précédent.
√ √
Remarque : d ne est la partie entière supérieure de n et se calcule avec les fonc-
tions ceil(sqrt(n)) (ajouter from math import ceil,log,sqrt en début de
fichier). Par ailleurs, L.index(x) retourne l’entier i si x se trouve à la i eme
place de la liste L (indexée à partir de 0), et retourne un message d’erreur dans
le cas contraire (par exemple : if x in L : i = L.index(x)).
Implémenter la fonction pas_de_bebe_pas_de_geant(g, h, N) et vérifier l’al-
gorithme en appellant cette fonction dans la fonction testDL avec le même
valeurs. Autrement dit, pas_de_bebe_pas_de_geant(3, 525, p-1) retourne
309 commme avec DLnaif.
Vecteur test (pour le debug) : première liste : [1, 99, 93, 308, 559, 329, 211, 664,
207, 268, 644, 654, 26, 147, 800, 727, 781, 464, 632, 275, 528, 496, 564, 15, 676,
586, 575, 295, 81]. Seconde liste : [525, 175, 328, 379, 396, 132, 44, 554, 724, 511,
440, 686, 768, 256, 355, 388, 399, 133, 314, 644]. Donc i = 10, j =19 et w = 29.
14
Si xi = yi , alors il existe αx , βx , αy , βy tels que g αx hβx = g αy hβy , c’est-à-dire
g αx −αy = hβy −βx . Donc k vérifie l’équation k(βy − βx ) = (αx − αy ) mod n, c’est
à dire k = (αx − αy )(βy − βx )−1 mod N (l’inverse existe si βy 6= βx mod N car
N est premier. Si βy = βx mod N , changer de valeurs initiales pour α0 , β0 ).
Notons que cet inverse (mod N ) n’est pas calculé dans le groupe G.
On a besoin de prendre une application f où les entiers αx , βx , αy , βy soient
facilement déterminés pour tout i. Pour cela, on définit une partition G0 ∪G1 ∪G2
de G, définie par x ∈ G =⇒ x ∈ Gx mod 3 . L’application f est définie par f (x)
= gx si x ∈ G0 , f (x) = hx si x ∈ G1 et f (x) = x2 si x ∈ G2 . La mise à jour des
valeurs de αx , βx , αy , βy se fait directement lors de l’appel à f (si x ∈ G0 alors
α est incrémenté de 1 modulo N , si x ∈ G1 alors β est incrémenté de 1 modulo
N et si x ∈ G2 alors α et β sont multipliés par 2 modulo N ). Cette fonction
prendra donc cinq paramètres f(h, g, x, alpha, beta, N) et en retourne 3.
Entrées : N , g et h vérifiant h = g k .
Sortie : l’entier k correspondant.
1. αx , βx = αy , βy = 1, 0
x = y = g αx hβx et i = 1
2. Répeter :
(a) x, αx , βx = f (h, g, x, αx , βx , N )
(b) y, αy , βy = f (h, g, y, αy , βy , N )
(c) y, αy , βy = f (h, g, y, αy , βy , N )
(d) i = i + 1
(e) Si x = y :
i. β = (βy − βx ) mod N
ii. Si β = 0 : retourner Echec
iii. Sinon : retourner (αx − αy ) * β −1 mod N
Comme p est premier, le groupe Z× p est d’ordre p − 1, donc n’est pas d’ordre
premier. Pour pouvoir tester l’algorithme sur un groupe d’ordre premier, on ne
considère plus le groupe entier, mais un sous groupe de Z× p engendré par un
élément d’ordre premier. Par exemple si p = 809 alors le sous groupe engendré
par g = 89 est un groupe d’ordre 101 (premier), tandis que si p = 633073699, g
= 1629 est d’ordre 27847 (premier).
Ecrire une fonction pollard_rho(g, h, N) qui impléménte cet algorithme et
compléter la fonction testDL en l’appelant avec g = 89, N = 101, h = 799
pour obtenir k = 50. La complexité en calcul de cet algorithme est la même
que la méthode pas de bébé, pas de géant (encore à cause du paradoxe des
anniversaires), mais la complexité en stockage de cette méthode est négligeable.
Vecteur test (pour le debug) pour x, y, αx , αy , βx , βy : 89, 89, 1, 1, 0, 0 avant la
boucle, puis 640, 72, 2, 2, 0, 1, puis 72, 640, 2, 3, 1, 2, puis 745, 745, 3, 4, 1, 3.
15
TP6 : Inversion d’une matrice
L’objectif est d’inverser une matrice à coefficients dans le corps Zp , avec p pre-
mier, à l’aide de l’algorithme du pivot de Gauss. Une application directe de ce
TP est donc la résolution des systèmes linéaires à coefficients dans Zp . Dans ce
TP on considère que les matrices sont inversibles (sauf à la fin du TP).
On définit une matrice carrée n×n comme une liste de n listes Li de longueur n,
où Li correspond à la ligne i de la matrice. On note ai,j le coefficient à la ligne
i, colonne j. Ainsi la liste de
3 listes A
= [[4, 1, 2], [2, 1, 0], [0, 4, 1]] correspond à
4 1 2
la matrice A ∈ M3,3 (Z5 ) : 2 1 0 et A[i][j] correspond à ai,j . Les lignes
0 4 1
et les colonnes d’une matrice carrée de taille n sont indexées de 0 à n − 1. Pour
afficher lisiblement une matrice on pourra utiliser la fonction Affiche_matrice.
L’algorithme du pivot de Gauss transforme une matrice A par des opérations sur
les lignes en appliquant la même opération sur la matrice identité. Dans ce TP
on modifie la matrice n × n A en une matrice n × 2n B composée de la matrice
A et de la matrice identité In : B = A k In , avec la fonction Modifie_matrice.
Ainsi cete fonction appliquée à la matrice précédente retourne la matrice B =
4 1 2 1 0 0
2 1 0 0 1 0 .
0 4 1 0 0 1
16
Par exemple, permutation(B, 0) modifie la matrice après l’appel à la fonction
précédente :
2 1 0 0 1 0
0 4 2 1 3 0 .
0 4 1 0 0 1
1. B = Modifie_matrix(A)
2. Pour i = 0 à n − 1 :
(a) Si bi,i = 0 alors permutation(B, i)
(b) Si bi,i 6= 1 alors Li ← b−1
i,i Li
(c) Pour j = i + 1 à n − 1 :
Si bj,i 6= 0 alors Lj ← Lj − bj,i Li
3. Pour i = n − 1 à 0 :
(a) Pour j = i − 1 à 0 :
Si bj,i 6= 0 alors Lj ← Lj − bj,i Li
4. Retourner la partie de droite de la matrice B
17
Exemple :
1 0 0 0 1 0 0 0
1 2 4 8 9 3 1 9
Vérifier que dans Z11 , l’inverse de
1
est
3 9 5 10 1 2 9
1 4 5 9 5 3 7 7
18
Nous allons tester la fonction Est_inversible de la manière suivante. Pour
construite une matrice inversible de Mn,n (Zp ), nous avons pn − 1 choix pour la
première colonne (tout est bon sauf la colonne nulle), puis pn − p choix pour la
seconde colonne (tout est bon sauf les multiples de la première colonne), puis
pn − p2 pour la troisième (on doit enlever les combinaisons linéaires des deux
premières), etc... jusqu’à pn − pn−1 choix pour la dernière colonne.
Qn−1
Ainsi la probabilité qu’une matrice de Mn,n (Zp ) soit inversible est ( i=0 (pn −
2 Qn−1 Qn−1 Qn−1
pi ))/pn = i=0 (pn −pi )/pn = i=0 ((pn−i −1)pi )/pn = i=0 (pn−i −1)/pn−i =
Qn−1 n−i n
) = i=1 (1 − 1/pi ). Ecrire la fonction rang_theorique(n, p)
Q
i=0 (1 − 1/p
qui retourne cette probabilité. Recalculer la par une fonction test_rang(n,t,p)
qui génère aléatoirement t matrices de Mn,n (Zp ) et calcule combien sont inver-
sibles (utiliser Matrice_aleatoire(n, p) qui est fournie). Vecteur test : vérifier
que rang_theorique(20, 2) retourne 0.29 et que rang_theorique(20, 5) re-
tourne 0.76 quand on arrondie les probabilités à deux chiffres après la virgule.
19