Algorithmique Numérique

publicité
Al g ori thmi q u e N u méri q u e
Rapport de TP "Gauss et compagnie"
Ce rapport présente deux algorithmes de produit matriciel (méthode "Classique" et de Strassen) ainsi
que deux algorithmes de résolution de systèmes linéaires. Le pivot de Gauss pour triangulariser la
matrice et la méthode de Gauss-Jordan qui diagonalise. La dernière partie porte sur le calcul du
déterminant d'une matrice avec deux algorithmes : celui de Jordan-Bareiss qui concerne le cas
particulier des matrices à cofficients entiers et celui du pivot de Gauss.
1. Le produit matriciel
a. Méthode classique
La méthode la plus simple pour calculer un
produit matriciel est de trouver les coefficients
un à un avec la formule ci-contre.
implémentation python avec
numpy
Exemples
>>> E1 = matrix( ( 2) )
>>> E2 = matrix( ( 3) )
>>> calc_num. prod_mat_classique( E1, E2)
array( [ [ 6. ] ] )
>>> E1 = matrix( ( 1, 2, 3, 4, 5, 6, 7, 8, 9) ) . reshape( 3, 3)
>>> E2 = matrix( ( 2, 4, 6, 8, 10, 12, 14, 16, 18) ) . reshape( 3, 3)
>>> calc_num. prod_mat_classique( E1, E2)
array( [ [ 60. ,
72. ,
84. ] ,
[ 132. , 162. , 192. ] ,
[ 204. , 252. , 300. ] ] )
rapport de TP "Gauss et Compagnie" - septembre 2008
1 /8
b. L'algorithme de Strassen
L'algorithme de Strassen permet de calculer un produit matriciel en effectuant moins de
multiplications, car la méthode "classique" n'est pas optimale.
Cet algorithme ne s'applique que sur les matrices dont la taille est une puissance de 2. Ce n'est pas
vraiment une limitation car n'importe quelle matrice peut devenir de cette forme en completant les
lignes et les colonnes par des 0.
L'algorithme de Strassen est récursif : à chaque étape la matrice est divisée en quatres sous-matrices,
l'amélioration consistant à effectuer des opérations plus simples entre celles-ci par rapport à la
méthode dite classique. Le cas d'arrêt de la récusivité est celui où les matrices sont de taille 1x1.
Comparatif entre les opérations des 2 méthodes, la méthode de Strassen n'utilise que 7
multiplications mais bien plus d'additions.
méthode de Strassen
méthode classique récursive
rapport de TP "Gauss et Compagnie" - septembre 2008
3 /8
Comparaison des méthodes classique et Strassen
La complexité du produit classique est grande en O(n3). L'algorithme de Strassen, en économisant une
multiplication coûteuse, permet de réduire cette complexité à O(n2,8) au prix d'un plus grand nombre
d'opérations d'additions. La différence ne doit apparaitre que sur de grandes matrices.
Le temps d'exécution a été obtenu avec le module python timeit et la commande suivante dans une
console :
pour la méthode classique
python - m timeit " import random; from numpy import *; import calc_num; a =
random. random( ( 4, 4) ) ; b = random. random( ( 4, 4) ) ; calc_num. prod_mat_classique( a, b) "
pour la méthode de Strassen
python - m timeit " import random; from numpy import *; import calc_num; a =
random. random( ( 4, 4) ) ; b = random. random( ( 4, 4) ) ; calc_num. prod_mat_strassen( a, b) "
pour la fonction intégrée de numpy (dot)
python - m timeit " import random; from numpy import *; a = random. random( ( 4, 4) ) ; b =
random. random( ( 4, 4) ) ; dot( a, b) "
Remarque : Les tests ont été fait sur un ordinateur équipé d'un Athlon XP à 1,3 GHz.
matrice 4x4
classique
758 usec
matrice 8x8
3,27 msec
matrice 32x32
176 msec
matrice 16x16
matrice 64x64
matrice 512x512
Strassen
2,89 msec
19 msec
22 msec
131 msec
1,43 sec
6,13 sec
880 msec
dot (numpy)
349 usec
362 usec
415 usec
622 usec
1,9 msec
2,38 sec
Contrairement à ce qui avait
été imaginé, l'algorithme de
Strassen ne prend pas le
dessus
sur
la
méthode
classique.
Plusieurs explications sont
possibles :
La
récursivité
rend
l'algorithme
de
Strassen
moins performant par rapport
à celui de la méthode
classique.
L'implémentation dans
un langage interprété comme
python
ralentit
les
2
algorithmes et repousse le
moment où l'algorithme de
Strassen prend l'avantage.
On remarque que la fonction interne de numpy est clairement plus performante, elle est
probablement compilée. Une version en C de l'algorithme de Strassen a été implementée pour tester
avec des matrices plus grandes mais le résultat était le même.
rapport de TP "Gauss et Compagnie" - septembre 2008
4/8
2. Résolution de systèmes linéaires
a. Méthode du pivot de Gauss
La méthode du pivot de Gauss propose de résoudre un système d'équations en triangonalisant la
matrice contenant les inconnues des équations. La dernière équation devient une simple égalité et on
remonte chaque ligne de la matrice en trouvant le résultat d'une nouvelle inconnue.
Exemple avec le système
d'équation :
La matrice correspondante est la
suivante :
>>> M
matrix( [ [ 1. , 2. , 2. , 2. ] ,
[ 1. , 3. , - 2. , - 1. ] ,
[ 3. , 5. , 8. , 8. ] ] )
>>> pivot_gauss( M)
matrix( [ [ 1. , 2. , 2. , 2. ] ,
[ 0. , 1. , - 4. , - 3. ] ,
[ 0. , 0. , - 2. , - 1. ] ] )
Une fois la matrice triangonalisée, la solution est rapide à trouver :
Remarque : L'algorithme du pivot de Gauss ci-dessus ne gère pas le cas où le pivot serait égal à 0 (ce
qui conduirai à une divison par zéro). La solution est de chercher un pivot non nul en échangeant les
lignes de la matrice. Cette solution a été implementée dans la méthode suivante et aurai pu l'être
aussi ici.
b. Méthode Gauss-Jordan
La méthode Gauss-Jordan est une variante du pivot de Gauss plus pratique en algorithmie. La matrice
des équations est diagonalisée au lieu d'être triangonalisée, la solution du système d'équation est
alors immédiate.
Sur l'exemple précedent :
>>> E5
matrix( [ [ 1. , 2. , 2. , 2. ] ,
[ 1. , 3. , - 2. , - 1. ] ,
[ 3. , 5. , 8. , 8. ] ] )
>>> gauss_j ordan( E5)
matrix( [ [ 1. , 0. , 0. , 3. ] ,
[ 0. , 1. , 0. , - 1. ] ,
[ 0. , 0. , 1. , 0. 5] ] )
rapport de TP "Gauss et Compagnie" - septembre 2008
5 /8
Le problème des très petites valeurs
Lorsque l'équation contient une très petite valeur, il faut éviter de
l'utiliser comme pivot. Dans l'exemple ci-contre, le coefficent de x est
très petit par rapport aux autres valeurs.
>>> E6
matrix( [ [ 1. 00000000e- 11,
1. 00000000e+00,
1. 00000000e+00] ,
[ 1. 00000000e+00,
1. 00000000e+00,
2. 00000000e+00] ] )
>>> gauss_j ordan( E6)
matrix( [ [ 1. , 0. , 1. ] ,
[ - 0. , 1. , 1. ] ] )
>>> set_printoptions( precision=12, suppress=False)
>>> gauss_j ordan( E6)
matrix( [ [ 1.
, 0.
, 1.
],
[ - 0.
, 1.
, 0. 99999999999] ] )
Résolution "à la main" :
Le calcul de x se fait avec des nombres très petits et
très grands :
>>> ( 2- 10e12) /( 1- 10e12)
0. 99999999999989997
La différence entre 2 et 1 se trouve "poussée" très loin
après la virgule, et lorsque l'on multiplie...
>>> ( 2- 10e12) /( 1- 10e12) *10e12
9999999999999. 0
↑ Il y a perte d'une précision significative
>>> 10e12 - ( 2- 10e12) /( 1- 10e12) *10e12
1. 0 ( la fraction a été " simplifiée" )
Le résultat final est moins précis. Afin de minimiser c'est
perte de précision il faut éviter de prendre des pivots très
petits.
rapport de TP "Gauss et Compagnie" - septembre 2008
6/8
Algorithme modifié
Dans cet algorithme, le plus
grand pivot est selectionné
par permutation.
Sur l'exemple précédent, la nouvelle méthode de selection du pivot change le résultat :
>>> E6
matrix( [ [ 1. 000000000000e- 11,
[ 1. 000000000000e+00,
>>> gauss_j ordan( E6)
matrix( [ [ 1.
, 0.
[ 0.
, 1.
1. 000000000000e+00,
1. 000000000000e+00,
,
,
La valeur de x a changé.
1. 000000000000e+00] ,
2. 000000000000e+00] ] )
1. 0000000000 1 ] ,
0. 99999999999] ] )
L'effet papillon
De petites variations sur les coefficients du système d'équations peuvent provoquer de grandes
différences sur la résolution. Dans l'exemple qui suit, le vecteur résultat change très peu et pourtant
la résolution est différente.
>>> E7
matrix( [ [ 10. ,
7. ,
[ 7. ,
5. ,
[ 9. ,
6. ,
[ 7. ,
5. ,
>>> gauss_j ordan( E7)
matrix( [ [ 1. , 0.
[ 0. , 1.
[ 0. , 0.
[ - 0. , - 0.
8. ,
6. ,
10. ,
9. ,
, 0.
, 0.
, 1.
, - 0.
7. ,
5. ,
9. ,
10. ,
,
,
,
,
0.
0.
0.
1.
32. ] ,
23. ] ,
33. ] ,
31. ] ] )
,
,
,
,
0. 09] ,
2. 55] ,
0. 55] ,
1. 27] ] )
rapport de TP "Gauss et Compagnie" - septembre 2008
>>> E8
matrix( [ [ 10. ,
7.
[ 7. ,
5.
[ 9. ,
6.
[ 7. ,
5.
>>> gauss_j ordan( E8)
matrix( [ [ 1. , 0.
[ 0. , 1.
[ 0. , 0.
[ - 0. , - 0.
,
,
,
,
8.
6.
10.
9.
, 0.
, 0.
, 1.
, - 0.
,
,
,
,
7.
5.
9.
10.
,
,
,
,
32. 1] ,
22. 9] ,
33. 1] ,
30. 9] ] )
,
,
,
,
0.
0.
0.
1.
,
,
,
,
0. 84] ,
1. 62] ,
0. 32] ,
1. 41] ] )
7 /8
3. Calcul de déterminants
a. En utilisant le pivot de Gauss
Dans le cas d'une matrice triangulaire, le calcul du determinant se simplifie en étant égal au produit
des coefficients diagonaux. L'utilisation de l'algorithme du pivot de Gauss précédent permet d'aboutir
facilement à un algorithme de calcul du déterminant.
Exemple :
>>> E10
matrix( [ [ 1. , 2. , 3. ] ,
[ 4. , 5. , 6. ] ,
[ 7. , 8. , 1. ] ] )
>>> det_gauss( E10)
24. 0
Calcul du déterminant avec la fonction
intégrée de numpy :
>>> from numpy. linalg import *
>>> det( E10)
23. 999999999999996
b. Algorithme de Jordan-Bareiss
Dans le cas particulier des matrices à coefficients entiers, l'algorithme de Jordan-Bareiss permet de
calculer le déterminant sans passer par les flottants. Cet algorithme permet d'obtenir un résultat plus
précis que la méthode précédente du pivot de Gauss.
Exemple :
>>> E11
matrix( [ [ 1, 2, 3] ,
[ 4, 5, 6] ,
[ 7, 8, 1] ] )
>>> from numpy. linalg import *
>>> det( E11)
23. 999999999999996
Cet exemple ne montre pas l'amélioration car les erreurs
d'arrondis de la méthode du pivot de Gauss ont joués
dans le "bon sens".
La seule amélioration est que le résultat est de type entier
(le déterminant d'une matrice à coefficents entiers est
entier).
>>> det_bareiss( E11)
24
rapport de TP "Gauss et Compagnie" - septembre 2008
8/8
Téléchargement