PC 2016/2017 Corrigé de la séance Python 3 : algèbre linéaire On commence par importer le module numpy : 1 import numpy as np 1 Systèmes linéaires 1. Pour effectuer la transvection Li ← Li + cLj , on peut faire une boucle (indexée par k) qui effectue les opérations A[i,k] = A[i,k] + c*A[j,k] (k variant de 0 à p − 1, où p est le nombre de colonnes de la matrice, i.e. la longueur du tableau A[0] par exemple). On peut aussi modifier le tableau A[i] en une seule étape : 1 2 3 4 1 2 3 1 2 3 1 2 3 def transvecLigne (A ,i ,j , c ): """ effectue l ’ op é ration L_i <- L_i + c * L_j sur A . Modifie la matrice A et ne renvoie rien """ A[i] = A[i] + c * A[j] Pour la permutation, la même syntaxe nécessite d’utiliser des copies des lignes (sinon, on se retrouverait avec une matrice ayant deux lignes égales). La solution consistant à utiliser une boucle ne nécessite évidemment pas de copie. def permutLigne (A , i1 , i2 ): """ permute les lignes d ’ indices i1 et i2 de A . """ A [ i1 ] , A [ i2 ] = np . copy ( A [ i2 ]) , np . copy ( A [ i1 ]) La dilatation : def dilatLigne (A ,i , c ): """ effectue l ’ op é ration L_i <- c * L_i sur A . """ A [ i ] *= c Le principe est identique pour les colonnes, aux notations près : A[i] désigne la ligne d’indice i, alors que A[:,j] désigne la colonne d’indice j. def transvecCol (A ,i ,j , c ): """ effectue l ’ op é ration C_i <- C_i + c * C_j sur A . """ A [: , i ] += c * A [: , j ] 4 5 6 7 def permutCol (A , j1 , j2 ): """ permute les colonnes d ’ indices j1 et j2 de A . """ A [: , j1 ] , A [: , j2 ] = np . copy ( A [: , j2 ]) , np . copy ( A [: , j1 ]) 8 9 10 11 def dilatCol (A ,j , c ): """ effectue l ’ op é ration C_i <- c * C_i sur A . """ A [: , j ] *= c 2. a) On commence par créer une copie de chacune des matrices A et B, sur lesquelles on va travailler (en les modifiant). Notons n le nombre de lignes de A (i.e. sa longueur). On commence par échelonner le système. Pour cela, on traite d’abord la première colonne (d’indice j = 0), puis la deuxième... et enfin l’avantdernière (d’indice n − 2). Le traitement de la colonne d’indice j consiste à utiliser le coefficient pivot Aj,j pour annuler (par des transvections) les coefficients Ai,j pour i > j (chaque manipulation effectuée sur les lignes de A doit l’être aussi sur les lignes de B). Il faut donc écrire deux boucles imbriquées l’une dans l’autre. Une fois le système échelonné, il faut pratiquer la phase de «remontée» pour annuler les coefficients situés au-dessus de la diagonale principale (chaque transvection pratiquée sur A doit encore l’être sur B). Pour cette phase de remontée, on commence par traiter la dernière colonne, puis l’avant dernière... Après cette phase de remontée, le système est diagonal, de la forme a1,1 x1 = b1 , . . . , an,n xn = bn . Reste à faire une dilatation sur chacune des lignes pour obtenir la valeur des xi . Comme la matrice A ne servira plus, on peut se dispenser de faire les dilatations sur A et ne les faire que sur le second membre B. Dans la version que nous proposons, cette dilatation est faite juste après avoir annulé tous les coefficients non diagonaux d’une colonne donnée. 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def systLin (A , B ): """ r é sout le syst è me AX = B ( B peut avoir plusieurs colonnes ) par pivot de Gauss . """ n = len ( A ) A1 = np . copy ( A ) B1 = np . copy ( B ) for j in range (n -1): for i in range ( j +1 , n ): c = A1 [i , j ] / A1 [j , j ] transvecLigne ( A1 ,i ,j , - c ) transvecLigne ( B1 ,i ,j , - c ) for j in range (n -1 , -1 , -1): for i in range (j -1 , -1 , -1): c = A1 [i , j ] / A1 [j , j ] transvecLigne ( A1 ,i ,j , - c ) transvecLigne ( B1 ,i ,j , - c ) dilatLigne ( B1 ,j ,1./ A1 [j , j ]) return ( B1 ) Si la matrice B comporte p colonnes, l’inconnue X doit aussi en comporter p ; la résolution du système A X1 · · · Xp = B1 · · · Bp équivaut à la résolution des p systèmes linéaires AXi = Bi . Lorsque l’on prend B = In , la solution calculée est la matrice A−1 . b) Chaque transvection sur une matrice effectue autant de multiplications que la matrice a de colonnes. Dans la phase de descente, on effectue donc, pour chaque passage dans la boucle indexée par i, une division et n + 1 multiplications. Comme i varie de j + 1 à n − 1, il y a (n − 1) − (j + 1) + 1 = n − j − 1 passages dans cette boucle. L’indice j varie de 0 à n − 1, donc il y a n−1 X (n − j − 1) = j=0 n−1 X j 0 =0 j0 = n(n − 1) 2 passages dans les boucles, pour un coût de n(n−1)(n+2) opérations (multiplications ou divisions). La 2 phase de remontée comporte autant d’opérations, ainsi que quelques dilatations. Au total, le nombre d’opérations est équivalent à n3 . En réalité, certaines des multiplications effectuées sont des multiplications pas 0 au cours des transvections (on pourrait d’ailleurs réécrire la fonction de transvection de façon à n’effectuer que les modifications utiles). Le nombre d’opérations autres que des opérations du type 0 + 0 · 0 est en fait équivalent à 23 n3 . 3. a) Pour coder les matrices, il faut que les coefficients soient des flottants (du moins, au moins un : le type étant homogène, si l’un des coefficient est un flottant et les autres des entiers, ils sont automatiquement tous convertis en flottants). 1 2 3 >>> A = np . array ([[1 e -20 ,1] ,[1 ,1]]) >>> B = np . array ([[1.] ,[2]]) >>> systLin (A , B ) array ([[ 0.] , [ 1.]]) La résolution fournit x = 0 et y = 1. La véritable solution est en fait donnée par −20 1 1 10 1 2 1 1 2 −1 2 · 10−20 − 1 −20 = = x = −20 ' 1 et y = '1 −20 10 10 −1 10−20 − 1 1 1 10 1 1 1 1 b) On parcourt les différents coefficients en gardant en mémoire l’indice du plus grand (en valeur absolue) rencontré depuis le début. 1 2 3 4 5 6 7 def pivot (A , j ): """ renvoie l ’ indice i du plus grand ( en valeur absolue ) des coefficients A [j , j ] , ... , A [n -1 , j ] """ n = len ( A ) imax = j for i in range ( j +1 , n ): if abs ( A [i , j ]) > abs ( A [ imax , j ]): 2 8 9 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 1 2 3 imax = i return ( imax ) c) Le principe est le même que pour la fonction précédente, à ceci près que, à chaque fois que l’on va traiter une colonne, on commence par chercher le plus grand pivot disponible et réaliser la permutation correspondante (ceci, uniquement dans la phase de triangularisation du système). def systLinPivot (A , B ): """ r é sout le syst è me AX = B ( B peut avoir plusieurs colonnes ) par pivot de Gauss en utilisant , dans chaque colonne , le plus grand pivot possible . """ n = len ( A ) A1 = np . copy ( A ) B1 = np . copy ( B ) for j in range (n -1): imax = pivot (A , j ) permutLigne ( A1 ,j , imax ) permutLigne ( B1 ,j , imax ) for i in range ( j +1 , n ): c = A1 [i , j ] / A1 [j , j ] transvecLigne ( A1 ,i ,j , - c ) transvecLigne ( B1 ,i ,j , - c ) for j in range (n -1 , -1 , -1): for i in range (j -1 , -1 , -1): c = A1 [i , j ] / A1 [j , j ] transvecLigne ( A1 ,i ,j , - c ) transvecLigne ( B1 ,i ,j , - c ) dilatLigne ( B1 ,j ,1./ A1 [j , j ]) return ( B1 ) Pour le système linéaire testé plus haut, on obtient cette fois-ci un résultat satisfaisant : >>> systLinPivot (A , B ) array ([[ 1.] , [ 1.]]) 4. On cherche encore à transformer la matrice A en une matrice triangulaire, par la même méthode du pivot. Cependant, chaque échange d’une ligne avec une autre change le signe du déterminant de la matrice ; il faut garder trace du nombre de permutations effectuées (une variable signe, valant ±1, conservera cette information). Si, après l’un des échanges de lignes pour utiliser le plus grand pivot disponible, celui-ci est nul, c’est que la matrice n’est pas inversible ; son déterminant est nul. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def determinant ( A ): n = len ( A ) A1 = np . copy ( A ) signe = 1 for j in range (n -1): imax = pivot ( A1 , j ) if imax > j : signe = - signe permutLigne ( A1 ,j , imax ) if A1 [j , j ] == 0: return (0) for i in range ( j +1 , n ): c = A1 [i , j ] / A1 [j , j ] transvecLigne ( A1 ,i ,j , - c ) produit = signe for j in range ( n ): produit *= A1 [j , j ] return ( produit ) 3 2 2.1 Décomposition LU Principe de la décomposition 1. L’algorithme, pratiqué à la main, fournit le couple 1 0 0 L = 2 1 0 et 3 72 1 1 4 −2 1. pour la matrice A = 2 6 3 5 3 1 U = 0 0 4 −2 −2 5 17 0 −2 2. 1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 8 9 1 2 3 4 def LU ( A ): """ renvoie le couple (L , U ) de la d é composition A = LU """ n = len ( A ) L = np . diag ([1.]* n ) U = np . copy ( A ) for j in range (n -1): for i in range ( j +1 , n ): c = U [i , j ] / U [j , j ] transvecLigne (U ,i ,j , - c ) transvecCol (L ,j ,i , c ) return (L , U ) Testons sur la matrice A (attention à bien la définir à coefficients flottants : sinon, le quotient non entier 7 2 sera converti en 3...). >>> A = np . array ([[1. ,4 , -2] ,[2 ,6 ,1] ,[3 ,5 ,3]]) >>> L , U = LU ( A ) >>> L , U ( array ([[ 1. , 0. , 0. ] , [ 2. , 1. , 0. ] , [ 3. , 3.5 , 1. ]]) , array ([[ 1. , 4. , -2. ] , [ 0. , -2. , 5. ] , [ 0. , 0. , -8.5]])) On peut vérifier que le produit est bien égal à A : >>> np . dot (L , U ) - A array ([[ 0. , 0. , 0.] , [ 0. , 0. , 0.] , [ 0. , 0. , 0.]]) 2.2 Algorithme de Thomas 3. Le produit de la k ème ligne de L par la k +1ème colonne de U fournit l’égalité βk ×0+1×γk +0×αk+1 = ck (pour k ∈ [[0, n − 2]]), d’où γk = ck . 4. Les produits matriciels donnent α0 = b0 et ∀k ∈ [[1, n − 1]], αk = bk − βk ck−1 , βk = ak . αk−1 La résolution du système LZ = Y donne alors z0 = y0 ∀k ∈ [[1, n − 1]], zk = yk − βk zk−1 . et La résolution du système U X = Z donne enfin xn−1 = zn−1 αn−1 et ∀k ∈ [[0, n − 2]], xk = zk − ck xk+1 . αk 5. On remarque qu’il n’est pas utile d’avoir deux tableaux x et z : un seul suffit, dans lequel on va d’abord stocker les coefficients zk , puis les remplacer par les coefficients xk . 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def thomas (a ,b ,c , y ): n = len ( a ) alpha = [0.] * n beta = [0.] * n x = [0.] * n alpha [0] = b [0] for i in range (1 , n ): beta [ i ] = a [ i ] / alpha [i -1] alpha [ i ] = b [ i ] - beta [ i ] * c [i -1] x [0] = y [0] for i in range (1 , n ): x [ i ] = y [ i ] - beta [ i ] * x [i -1] x [n -1] = x [n -1] / alpha [n -1] for i in range (n -2 , -1 , -1): x [ i ] = ( x [ i ] - c [ i ] * x [ i +1]) / alpha [ i ] return ( x ) 2.3 Le cas général : P A = LU 6. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1 2 3 4 5 6 7 8 9 10 def PLU ( A ): n = len ( A ) P = np . diag ([1.]* n ) L = np . diag ([1.]* n ) U = np . copy ( A ) for j in range ( n ): imax = pivot (U , j ) if imax > j : permutLigne (U ,j , imax ) permutCol (L ,j , imax ) permutLigne (L ,j , imax ) permutLigne (P ,j , imax ) for i in range ( j +1 , n ): c = U [i , j ] / U [j , j ] transvecLigne (U ,i ,j , - c ) transvecCol (L ,j ,i , c ) return (P ,L , U ) Pour la même matrice A que plus haut, on trouve >>> PLU ( A ) ( array ([[ 0. , 0. , 1.] , [ 0. , 1. , 0.] , [ 1. , 0. , 0.]]) , array ([[ 1. , 0. , 0. [ 0.66666667 , 1. , 0. [ 0.33333333 , 0.875 , 1. array ([[ 3. , 5. , 3. [ 0. , 2.66666667 , -1. [ 0. , 0. , -2.125 5 ], ], ]]) , ], ], ]]))