0% ont trouvé ce document utile (0 vote)
39 vues19 pages

TP d'Algebre pour 1A Informatique

Transféré par

Anas Kartaoui
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
39 vues19 pages

TP d'Algebre pour 1A Informatique

Transféré par

Anas Kartaoui
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

TP d’algèbre

1A informatique

Déroulement général : chaque TP est à faire en monôme. Déposer le fi-


chier source python sur la plateforme, au plus tard une semaine après la fin du
TP (sauf en cas d’une autre consigne). Ne déposer qu’un seul fichier par TP
(ne pas faire un fichier par exercice, ni de fichier readme). Commenter le code
directement dans le fichier source (notamment les théorèmes utilisés).

Notation : la note de TP prend en compte les éléments suivants : présence


et retard en séance, quantité de travail pendant la séance, efficacité du travail
pendant la séance, fonctionalité du code rendu et qualité du code rendu. Les ab-
sences injustifiées donnent une note de 0 au TP sauf en cas d’accord exceptionnel
avec l’encadrant de TP.

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

Consignes sur le plagiat : regarder le code source d’une autre personne,


chercher du code sur internet ou sur des outils tels que chatgpt, ou travailler à
plusieurs n’est pas interdit. Cela ne doit toutefois pas servir d’excuse pour du
plagiat. Récupérer du code source extérieur en le modifiant à la marge (nom de
variable, commentaires, etc...) est du plagiat. Au moindre doute, écrire l’origine
des sources extérieures en commentaires (code récupéré de tel site internet,
auprès de telle personne, ...). Le cas échéant, laisser votre propre code source en
commentaire, en expliquant que celui-ci ne fonctionnant pas, vous avez du vous
tourner vers un autre code.

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.

Entrées : x, y ∈ Z. Sortie : le pgcd(x, y) > 0.


1. Initialisation : x = |x| et y = |y|.
2. Tant que y 6= 0 :
(a) r = x mod y
(b) x = y
(c) y = r
3. Retourner x.

Ecrire une fonction Test_pgcd() qui vérifie que pgcd(2, 3) = pgcd(1, -2) = 1
et pgcd(2, 0) = pgcd(-2, 4) = 2.

Application de l’exercice 1 : On montre que la probablité que deux entiers


soient premiers entre eux est 6/π 2 ' 0.608. Vérifier ce résultat avec une fonction
Test_proba() qui génère 1000 fois deux entiers aléatoires entre 1 et 21024 , puis
fait appel à la fonction précédente pour calculer cette probabilité.
Remarques : Utiliser x = randint(1, 1<<1024) pour la génération de ces en-
tiers, en vérifiant pourquoi la notation << produit le résultat attendu (avec
from random import randint). Ne pas utiliser la division entière pour le cal-
cul de la probabilité. Afficher le résultat avec 3 décimales à l’aide d’une ligne du
type print("Proba: {:.3f}".format(test_proba(1000)))

Exercice 2 : écrire une fonction Bezout(x, y) qui prend en entrée deux


entiers x et y et retourne le pgcd(x, y) et les coefficients de Bezout u et v à
l’aide de l’algorithme d’Euclide étendu ci-dessous. La complexité de l’algorithme
d’Euclide étendu est similaire à celle de l’algorithme d’Euclide. Attention : on
veut que le pgcd retourné soit positif, on change donc le signe des trois valeurs
retournées si le pgcd est négatif.

Entrées : x, y ∈ Z. Sortie : d, u et v tels que x.u + y.v = d = pgcd(x, y) > 0.


1. (u0 , u1 )=(1, 0) et (v0 , v1 )=(0, 1).
2. Tant que y 6= 0 :
(a) x = yq + r (division euclidienne)
(b) (x, y) ← (y, r)
(c) (u0 , u1 ) ← (u1 , u0 − qu1 )
(d) (v0 , v1 ) ← (v1 , v0 − qv1 )
3. Retourner x, u0 , v0 .

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 .

Écrire une fonction inverse(x, n) qui prend en entrée un entier n et un entier


x, fait appel à la fonction Bezout, et qui retourne l’inverse de x modulo n si
celui-ci est inversible et 0 sinon, en affichant un message d’erreur. On utilisera
la fonction pgcd pour savoir si l’entier x est inversible. L’élément retourné doit
être compris entre 0 et n − 1 (calculer la sortie modulo n).
Vérifier que l’inverse de 3 mod 21024 est 119846208990821060515287012719268
315574531798596153771515620054105155117203667308755138984881605024014
080075919914262238439193179209611081661898287092982749585178595616576
990184201479734164062746302055301390003845892100454894975254315942073
693884824775567007123057532159964830625653144203223570886416149424811.
Ecrire une fonction test_inverse() qui vérifie aussi que le produit de 3 et de
l’inverse précédent fait bien 1 modulo 21024 .

Exercice 3 : écrire une fonction theoreme_chinois(L1, L2) qui prend en


entrée deux listes L1 = (x1 , .., xn ) et L2 = (p1 , .., pn ) de même taille où chaque
0 ≤ xi < pi et où les entiers pi sont premiers entre eux et qui retourne l’unique
solution x entre 0 et N = p1 × . . . × pn vérifiant le système congruentiel

 x ≡ x1 mod p1
...
x ≡ xn mod pn

à l’aide du théorème chinois. Tester le programme en vérifiant que la fonction


theoreme_chinois([3, 4, 5], [17, 11, 6]) retourne 785. Si ce n’est pas le
cas, utiliser la solution vue en cours pour corriger le programme.
Remarque : sous python3, a/b retourne un nombre réel même si a et b sont
entiers alors que le quotient de la division euclidienne a//b retourne un entier.

La complexité de l’algorithme revient à peu près à n appels à l’algorithme


d’Euclide étendu. Celui-ci est donc toujours de complexité polynomiale. Vérifier
que la plus petite solution positive au système suivant :

 x ≡ 3 mod 21024 − 1
x ≡ 4 mod 21024
x ≡ 5 mod 21024 + 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) :

Entrées : un entier p dont on veut tester la primalité et un entier 0 <


a < p − 1. Sortie : True ou False selon la primalité de p.
1. Calculer (k, q) tels que p − 1 = 2k q, avec q impair.
2. Calculer b = aq mod p.
3. Si b = 1 ou b = p − 1 retourner True.
4. Tant que k > 0 :
(a) b = b2 mod p
(b) Si b = p − 1 retourner True
(c) k = k − 1
5. Retourner False

Remarque 1 : la fonction pow(a, b, c) retourne ab mod c de manière efficace.


Il existe aussi une fonction pow de la librairie math qui ne prend que deux argu-
ments. Ainsi si on écrit import math, l’utilisation d e pow avec trois arguments
produira une erreur. Pour éviter cela, si on a besoin d’une fonction de la li-
brairie math, on écrira plutôt from math import fonction.
Remarque 2 : Il est fortement conseillé d’écrire une petite fonction décompose(),
qui retourne k et q, pour la premiere étape de l’algorithme précédent.
Pour le debug, les valeurs intermédiaires pour temoin_Miller_Rabin(7,121)
(qui retourne False) : Avant la boucle : k = 3, q = 15, b = 87.
A la fin de chaque tour de boucle : b = 67, k = 2, b = 12, k = 1, b = 23, k =
0. Il n’est pas nécessaire de laisser ces valeurs dans le code source, elles ne sont
données que pour le debug, si besoin !
2k k−1 k−1
1. En effet aq − 1 = (a(2 )q + 1)(a(2 )q − 1) et on recommence. Par exemple a8 − 1

= (a4 + 1)(a4 − 1) = (a4 + 1)(a2 + 1)(a2 − 1) = (a4 + 1)(a2 + 1)(a − 1)(a + 1)

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.

Exercice 2 : Il n’est pas nécessaire de générer les entiers a (appelés témoins) de


manière aléatoire. On peut prendre les 40 plus petits entiers premiers, contenus
dans la liste [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163,
167, 173]. Réécrire une fonction Miller_Rabin_40(p) qui teste la primalité de
p en parcourant les témoins dans la liste ci-dessus.
Même si le test de Miller-Rabin est très efficace, il est dommage de l’utiliser
pour des entiers divisibles par de petits premiers. L’idée est donc de tester si un
entier p est divisible par les n plus petits entiers premiers avant de faire appel au
test de Miller-Rabin. Un fichier primes_100000.txt contient les 100000 petits
entiers premiers sur foad (remarque : le choix n = 100000 a été choisi pour que
le fichier ne soit pas trop volumineux, sinon il pourrait être plus grand).
Ecrire la fonction Recupere_premiers qui retourne une liste d’entiers premiers :
def Recupere_premiers(filename):
f = open(filename,’r’)
premiers = f.readlines()
f.close()
return [int(p) for p in premiers]
Ecrire une fonction test_primalite(p, liste_premiers) qui vérifie que p
n’est pas divisible par un entier de liste_premiers (obtenu par la fonction
Recupere_premiers), puis appelle Miller_Rabin_40(p) si ce n’est pas le cas.
Attention, si p est dans liste_premiers, il est premier, le terme multiple
précédent doit donc être compris avec précaution.
Nous allons tester la fonction précédente avec un exemple historique : Fermat
n
conjectura que les entiers de la forme 22 + 1 avec n ≥ 0 étaient premiers. Cela
s’avère faux dès que n = 5. Tester la fonction précédente sur ces nombres au
sein d’une fonction Test_Fermat().

Ecrire une fonction generer_premiers(n, liste_premiers) qui retourne un


nombre premier aléatoire de n bits : on génère un entier de n bits avec randint,
si cet entier est pair, on l’incrémente de 1, puis on teste s’il est premier avec
la fonction précédente, et si ce n’est pas le cas, on l’incrémente de 2 jusqu’à ce
qu’il soit premier (on évite de tester la primalité des entiers pairs).

7
TP3 : Factorisation

Soient p et q deux grands nombres premiers et N = pq. L’objectif de ce TP est


de mettre en oeuvre des algorithmes pour factoriser N qui soient meilleurs√ que
la stratégie de la division successive de tous les entiers inférieurs à N .

Exercice 1 (méthode naı̈ve) : Ecrire une fonction FactorisationNaive(N)


qui retourne p et q de √
la manière suivante
√ : on initialise une variable x à la partie
entière supérieure de N , notée d N e, en utilisant les fonctions ceil et sqrt
de la librairie mathématique. Tant que x ne divise pas N (utiliser le modulo),
on incrémente x de 1. Une fois que p est trouvé, on obtient q en divisant N par
p. Faire en sorte que la fonction retourne aussi le nombre d’itération.
Ecrire une fonction testFactorisationNaive qui appelle la fonction précédente
avec 47941
√ et retrouve les facteurs premiers 251 et 191 en 32 itérations, en notant
que d 47941e = 219 et que 219 + 32 = 251.

Exercice 2 (méthode de Fermat) : Le principe de la méthode de Fermat


est de chercher des entiers x et y tels que x2 − y 2 = N . Dans ce cas les deux
facteurs non triviaux
√ de N sont x + y et x − y. La méthode de Fermat consiste à
initialiser x à d N e et à incrémenter x de 1 jusqu’à ce√que x2 − N soit√un carré.
Dans ce cas, les deux entiers p et q cherchés sont x + x2 − N et x − x2 − N .
Écrire une fonction estCarre(x) qui retourne True√si x est un carré et False
autrement. Pour cela calculer la partie entière z de x et vérifier si z 2 = x.
Écrire une fonction Fermat(N) qui retourne p et q à l’aide de la méthode de
Fermat décrite ci-dessus, ainsi que le nombre d’itérations.
Ecrire une fonction testFermat qui appelle la fonction précédente avec 47941
et retrouve les facteurs premiers 251 et 191 en 2 itérations, en notant que 2212
= 48841 et que 48841 − 47941 = 900 qui est le carré de 30.

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

Exercice 3 (complexité) : Nous allons maintenant étudier et comparer


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 factorisationNaive(N) est p - d N e. Pour la
fonction Fermat(N), on remarque que N = pq = ( p+q 2 p−q 2
2 ) − ( 2 ) . On a donc
p+q
√ 2
réalisé 2 − d N e itérations (en effet le nombre x tel que x − N soit un carré

est p+q2 et on a initialisé x à d N e).

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.

Exercice 4 (méthode ρ de Pollard) : soient E un ensemble à N éléments,


f : E → E une application et a ∈ E. On considère la suite (xi )i définie par x0
= a et xi = f (xi−1 ). Alors cette suite est ultimement périodique, c’est-à-dire
qu’il existe un indice i0 ≥ 0 et une période T , avec 1 ≤ T ≤ N tels que pour
tout i ≥ 0, on a xi0 +i = xi0 +i+T (vérifier pourquoi si besoin). L’algorithme du
lièvre et de la tortue retourne un multiple t de cette période :

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

La méthode ρ de Pollard s’inspire directement de cet algorithme pour factoriser


N . En effet, à chaque tour de boucle, si l’égalité x = y mod p est vérifiée (ce

qui arrive en moyenne après O( p) tours) alors p divise x − y et donc p =
pgcd(x − y, N ), sauf si x = y mod N , ce qui serait pas de chance. Par contre
p étant inconnu, on ne peut pas vérifier directement si x = y mod p, on vérifie
donc si pgcd(x − y, N ) > 1, que l’on retourne si c’est le cas.
Ecrire une fonction PollardRho(N) qui retourne p, q et le nombre d’itération à
l’aide le l’algorithme précédent, en utilisant la fonction f (x) = x2 + b mod N et
a = b = 1 Importer la fonction pgcd du premier TP ou utiliser la fonction gcd
du module math. Définir f comme une lambda fonction, c’est-à-dire définir f
dans la fonction par la ligne suivante : f = lambda x: (x * x + b) % N, puis
l’appeler comme une fonction classique.
Ecrire une fonction testPollardRho qui factorise 47941 avec la fonction précédente
en 15 itérations. Valeurs intermédiaires en cas de debug dans la boucle :
x = 2, y = 5, d = 1 ; x = 5, y = 677, d = 1 ; . . . ; x = 1307, y = 47041, d = 1 ;
x = 30315, y = 30697, d = 191 (15 tours de boucle).
Ecrire une fonction ComplexitePollardRho(n) qui génère deux nombres pre-
miers aléatoires p et q de n bits (utiliser n = 32), appele les deux fonctions
précédentes (Fermat et ρ-Pollard) avec N = pq et compare le nombre d’itérations.
Cela dépend de p et q mais en moyenne l’algorithme de Pollard est bien plus
efficace, même si la complexité de ce dernier reste élevé).

9
TP4 : Fractions continues

Le développement en fraction continue de x ∈ R se calcule de la manière sui-


vante. On note a0 = bxc sa partie entière, et b0 = x − a0 sa partie décimale.
Alors on peut écrire x = a0 + b0 = a0 + 11 , puis recommencer le processus
b0

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

Par exemple, le développement


√ en factions continues des racines carrées irra-
tionelles comme 2 a été étudié par les algébristes italiens de la renaissance
comme Bombelli et Cataldi :
√ 1
2=1+
1
2+
.. 1
.+
2 + bn
Pour tout x ∈ R, on note rn = [a0 , . . . , an ] l’approximation rationelle de x,
obtenue en posant bn = 0 dans le développement en fraction continue précédent.
Dans ce cas rn est appelée réduite d’ordre n de x. On peut montrer que x ∈
Q ⇐⇒ ∃n ∈ N, x = [a0 , . . . , an ], c’est-à-dire ∃n ∈ N, bn = 0.
Ces réduites rn = un /vn de x se calculent facilement à l’aide des récurrences :
u0 = 1, v0 = 0, u1 = a0 , v1 = 1. Pour n ≥ 2 : un = an−1 un−1 + un−2 et vn =
an−1 vn−1 + vn−2 (par récurrence, après l’avoir vérifié pour n = 0, 1 et 2).

Exercice 1 (fraction continue d’un rationnel) : soit x = p/q ∈ Q et ai ,


bi les quotients et restes des divisions euclidiennes obtenues avec l’algorithme
d’Euclide pour le calcul du pgcd(p, q). Alors on a : p = a0 q + b0 , donc

b0 1 1 1
x = a0 + = a0 + q = a0 + b1
= a0 + 1 = ...
q b0 a1 + b0
a1 + b0
b1

Soit n l’entier tel que le reste bn = 0, alors on a x = [a0 , . . . an ]. La liste des ai


de p/q est donc obtenue par l’algorithme d’Euclide pour le calcul de pgcd(p, q),
puisque c’est exactement la liste des quotients obtenus à chaque tour de l’algo-
rithme. On calcule alors les suites un et vn (et donc rn ) à partir de ce quotients.

Ecrire une fonction Reduites_rationnels(p, q) qui retourne la liste des réduites


de p/q. Vecteur test : Reduites_rationnels(37, 23) retourne [1, 2, 1.5, 1.666,
1.6, 1.608], en notant que la liste des quotients partiels pour 37/23 est [1, 1, 1, 1, 1, 4]
et que l’on a un = [1, 1, 2, 3, 5, 8, 37] et vn = [0, 1, 1, 2, 3, 5, 23]. Second vecteur
test : Reduites_rationnels(37, 5) retourne un = [7, 15, 37] et vn = [1, 2, 5].

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.

Exercice 3 (équations de √ Pell-Fermat) : Soit A ≥ 2 un entier qui n’est pas


un carré (c’est à dire que A est irrationnel). Une équation de Pell-Fermat est
une équation diophantienne de la forme X 2 − AY 2 = d avec d entier non nul.
L’objectif est de déterminer la plus petite solution entière, autre que (1, 0), de
cette équation avec d = 1 . On peut résoudre cette équation à l’aide des fractions

continues : on montre que si (x, y) est solution
√ alors
√ x/y est une réduite√ de A.
En effet, (x, y) est solution =⇒ | xy − A|| xy + A| = y12 avec | xy + A| ≥ 2

(car x ≥ y puisque x2 = 1 + Ay 2 donc x/y ≥ 1). Donc | A − xy | < 2y12 , ce qui

implique que x/y est une réduite de √ A (cette dernière implication est admise,
mais remarquer l’approximation de A par un rationnel que cela implique).

Ecrire une fonction equation_Pell_Fermat(A) qui retourne cette solution. Re-


marque : le plus simple est de réécrire totalement la fonction sans faire appel à
la fonction précédentes. Dans ce cas, au lieu d’utiliser une boucle for, on utilise
une boucle while qui boucle tant que x2 − Ay 2 6= 1 pour la la réduite x/y. Tester
cette fonction pour A entre 2 et 60 où A n’est pas un carré. Pour A = 2, on a
un = [1, 3] et vn = [1, 2] qui s’arrête car u21 − 2v12 = 1. Pour A = 7, on a un =
[1, 2, 3, 5, 8] et vn = [0, 1, 1, 2, 3] qui vérifie 82 − 7 × 32 = 1

Exercice 4 (problème d’arithmétique flottante) : tester la fonction précédente


avec A = 61. Normalement votre programme ne trouve pas de solution. Vérifier
que (1766319049, 226153980) est solution de l’équation. Le cas 61 a été proposé
par Fermat dans sa correspondance de 1657 : Quel est, par exemple, le plus petit
carré qui, multipliant 61, en prenant l’unité, fasse un carré ?

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.

Exercice 1 (exponentiation) : l’algorithme suivant (appelé square and mul-


tiply) permet de faire une exponentiation efficacement. Implémenter cet algo-
rithme dans une fonction exp(g, k), où k ≥ 0, l’élément neutre de G est e = 1
et le produit de deux éléments de G fait appel à la fonction loi :
Pt
Entrées : un élément g ∈ G, un entier k = i=0 ki 2 i .
Sortie : h = g k ∈ G.
1. h ← e.
2. Pour i = t à 0 :
(a) h ← h2 .
(b) Si ki = 1 : h ← hg.
3. Retourner h.

Remarques : range(t,-1,-1) est la liste [t, t − 1, . . . , 1, 0]. Par ailleurs, on ob-


tient la taille binaire t de l’entier k avec le logarithme en base deux, obtenu
par t = int(math.log(k, 2)) + 1. Enfin Le i-eme bit ki de k s’obtient par
l’expression (k>>i) & 1 (vérifier pourquoi).
Ecrire une fonction testExp qui appelle la fonction précédente avec g = 3, k =
309 et p = 809 et qui est sensée retourner h = 525. Vecteur test pour le debug
avec les valeurs de h intermédiaires 1, 1, 3, 9, 81, 267, 291, 545, 366, 471, 309
(une ou deux valeur par tour de boucle).
Remarque : l’algorithme précédent fonctionne grace à la propriété suivante :
k = k0 + 2(k1 + 2(k2 + . . . + 2(kn−1 + 2kn ) . . .)), ce qui implique :
g k = g k0 (g k1 (g k2 (. . . (g kn−1 (g kn )2 )2 . . .)2 )2 .

Exercice 2 (méthode de Shanks) : l’objectif de cet exercice est de résoudre


le problème du logarithme discret, dans un premier temps de manière naı̈ve
(O(n) opérations), puis à l’aide de l’algorithme
√ pas de bébé, pas de√géant (baby
step giant step) dont la complexité est en O( n) opérations, et O( n) données
à stocker, proposée par D. Shanks.
L’algorithme DL_naif(g, h) suivant consiste à calculer les puissances succes-
sives de g jusqu’à retrouver l’élément h :

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.

L’algorithme pas de bébé, pas de géant présenté ci-dessous


√ permet de re-
trouver l’entier k de manière plus efficace : Soient w = d ne et k = wq + r la
division euclidienne de k par w (avec 0 ≤ q, r ≤ k). Alors h = g k = g wq+r , c’est
à dire hg −r = g wq . Remarquons que g −r = g n−r (théorème de Lagrange).

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.

Exercice 3 (méthode ρ de Pollard) : pour simplifier, on considère que


l’ordre N du groupe G est premier, toujours de générateur g, et h = g k . Soient
(xi ) et (yi ), deux suites dans G, initialisées par x0 = y0 = g α0 hβ0 (avec 0 ≤
α0 , β0 ≤ N − 1), définies par xi = f (xi−1 ) et yi = f (f (yi−1 )), avec f : G → G.

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

Avant de décrire l’algorithme du pivot de Gauss, on a besoin d’utiliser trois


fonctions intermédiaires à implémenter dans un premier temps :
— La fonction Multiplie_ligne(B, i, c, p) modifie la ligne i de la ma-
trice B en multipliant chaque coefficient de cette ligne par c dans Zp (la
taille de la matrice est calculée avec la fonction len()).
— La fonction Modifie_ligne(B, i, j, c, p) modifie la ligne i de la
matrice B en ajoutant à la ligne i la ligne j multipliée par c (dans Zp ).
— La fonction permutation(B, i) modifie la matrice B en permutant la
ligne i avec la premiere ligne indexée à partir de i+1 pour que le nouveau
coefficient diagonal soit non nul. Cette fonction retourne True si il y a
eu permutation et False sinon.
Par exemple, Multiplie_ligne(B, 0, 2, 5) modifie la matrice :
 
3 2 4 2 0 0
 2 1 0 0 1 0 .
0 4 1 0 0 1

Par exemple, Modifie_ligne(B, 0, 1, 3, 5) modifie la matrice :


 
0 4 2 1 3 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

On va maintenant écrire la fonction Inverse_matrice(A, p) qui retourne une


matrice B, inverse de A, en se basant sur l’algorithme du pivot de Gauss (en
notant Li la ligne d’indice i) où toutes les opérations sont réalisées modulo p :

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

Pour réaliser la dernière opération, on pourra utiliser les lignes suivantes :


for i in range(n) :
for j in range(n) :
B[i].pop(0)

Pour calculer l’inverse d’un entier modulo p, utiliser la fonction du TP précédent.


Si besoin l’inverse modulaire peut aussi se calculer avec la fonction inverse du
module Crypto.Util.number

Exemple : on applique l’algorithme


 la matrice B (indexée de 0 à
précédent à
4 1 2 1 0 0
n − 1), en partant de B =  2 1 0 0 1 0 .
0 4 1 0 0 1
On peut afficher les résultats intermédiaires dans la fonction avec des Affiche_matrice(B) :
   
1 4 3 4 0 0 1 4 3 4 0 0
L0 ← 4L0 :  2 1 0 0 1 0  L1 ← L1 − 2L0 :  0 3 4 2 1 0 
0 4 1 0 0 1 0 4 1 0 0 1
   
1 4 3 4 0 0 1 4 3 4 0 0
L1 ← 2L1 :  0 1 3 4 2 0  L2 ← L2 − 4L1 :  0 1 3 4 2 0 
 0 4 1 0 0 1   0 0 4 4 2 1 
1 4 3 4 0 0 1 4 3 4 0 0
L2 ← 4L2 :  0 1 3 4 2 0  L1 ← L1 − 3L2 :  0 1 0 1 3 3 
0 0 1 1 3 4  0 0 1 1 3 4
 
1 4 0 1 1 3 1 0 0 2 4 1
L0 ← L0 −3L2 :  0 1 0 1 3 3  L0 ← L0 −4L1 :  0 1 0 1 3 3 
0 0 1 1 3 4 0 0 1 1 3 4

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

Calcul du déterminant : si on calcule le déterminant d’une matrice carrée


n × n par la méthode des cofacteurs, cela nécessite n calculs de déterminants
de matrices (n − 1) × (n − 1), soit n(n − 1) calculs de déterminants de matrices
(n − 2) × (n − 2), etc ... Pour arriver au final à n! opérations (addition ou mul-
tiplication) sur les éléments de Zp , ce qui n’est pas de complexité polynomiale.

L’algorithme du pivot de Gauss est de complexité polynomiale (plus précisément


en O(n3 ) opérations). On peut utiliser cet algorithme pour calculer le déterminant
en temps polynomial. Pour cela, on remarque que le determinant d’une matrice
identité vaut 1. De plus en notant A0 la matrice obtenue à partir de la matrice
A à l’aide de l’une des opérations suivantes (où λ ∈ Zp ), on peut obtenir det(A0 )
à partir de A par :
— Si Li ← λLi (avec λ 6= 0) alors det (A0 ) = λ det(A).
— Si Li ← Li + λLj (avec i 6= j) alors det (A0 ) = det(A).
— Si Li est permuté avec Lj (avec i 6= j) alors det(A0 ) = - det(A).
Pour calculer le déterminant, on initialise une variable d à 1 (le déterminant
de la matrice identité) puis on modifie d à chaque opération sur la matrice. A
la fin de l’algorithme on obtient donc le déterminant de la matrice inverse. On
retrouve le déterminant de la matrice originale en calculant l’inverse (modulaire)
de ce déterminant car si une matrice A est inversible alors det(A)−1 = det(A−1 ).

Modifier la fonction précédente pour caculer aussi le déterminant de la matrice.


Vérifier la fonction en contrôlant que le determinant de la matrice A (dans Z5 )
est 3 et que celui de la matrice C (dans Z11 ) est 4.

Probabilité qu’une matrice soit inversible : il est possible de modifier la


fonction précédente pour calculer le rang de la matrice (l’algorithme présenté ici
ne fonctionne que si la matrice est inversible donc de rang maximal), mais nous
allons étudier une version plus légère, celui de savoir si la matrice est inversible.
On ne peut pas utiliser le calcul du determinant car celui-ci utilise l’hypothèse
que la matrice est inversible (vous pouvez tester avec la matrice nulle).

Pour cela, on part de la fonction précédente, que l’on renomme Est_inversible.


Travailler directement sur la matrice, sans passer par une seconde matrice (enle-
ver notamment Modifie_matrice et les lignes de la fin pour récupérer la partie
droite). Enlever aussi la partie 3 de l’algorithme qui est inutile pour le calcul de
rang. Enfin, à chaque appel à permutation, si celle-ci retourne False, alors la
matrice n’est pas inversible (vérifier le si ce n’est pas clair) et donc la fonction
retourne False. A la fin, la fonction retourne True car si on arrive là c’est que
la matrice est inversible.

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.

Appeler les deux fonctions pour t = 100, n = 20 et p l’ensemble des entiers


premiers ≤ 173 (voir TP sur la primalité) et récupérer ces probabilités sous
forme de deux listes L1_r et L2_r. Afficher sur un graphique ces deux listes
(utiliser la fonction Affiche_rang qui est fournie).

19

Vous aimerez peut-être aussi