Corrigé de la séance Python 3 : algèbre linéaire 1

publicité
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
],
],
]]) ,
],
],
]]))
Téléchargement