Dans Xcas, il existe déjà les fonctions qui correspondent aux algorithmes qui suivent, ce sont : ref, rref, ker, pivot
Étant donné un système d’équations noté AX=b, on cherche à le
remplacer par un système équivalent et triangulaire inférieur.
À un système d’équations AX=b, on associe la matrice M formée
par A que l’on borde avec b comme dernière colonne.
La méthode de Gauss (resp Gauss-Jordan) consiste à multiplier A et b
(donc M) par des matrices inversibles, afin de rendre A triangulaire
inférieure (resp diagonale). Cette transformation, qui se fera au coup
par coup en traitant toutes les colonnes de A (donc toutes les colonnes de
M, sauf la dernière), consiste par des combinaisons de lignes de M
à mettre des zéros sous (resp de part et d’autre) la diagonale de A.
La fonction gauss_redi ci-dessous, transforme M par la méthode de
Gauss, la variable pivo (car pivot est une fonction de Xcas)
sert à mettre le pivot choisi.
Pour j=0..p−2 (p−2 car on ne traite pas la dernière colonne de M),
dans chaque colonne j, on cherche ce qui va faire office de pivot à partir de la diagonale : dans le programme ci-dessous on choisit le premier
élément non nul, puis par un échange de lignes, on met le pivot sur la
diagonale (pivo=M[j,j]). Il ne reste plus qu’à former, pour chaque ligne
k (k>j) et pour a=M[k,j], la combinaison :
pivo*lignek−a*lignej (et ainsi M[k,j] devient nul).
On écrit pour réaliser cette combinaison :
a:=M[k,j];
for (l:=j;l<nc;l++){
M[k,l]:=M[k,l]*pivo-M[j,l]*a;}
On remarquera qu’il suffit que l parte de j car :
pour tout l<j, on a déjà obtenu, par le traitement des colonnes
l=0..j−1, M[k,l]=0.
Le programme ci-dessous ne sera utile que si on trouve un pivot pour
chaque colonne : c’est à dire si la matrice A est de rang maximum.
En effet, dans ce programme, si on ne trouve pas de pivot (i. e. si tous les
éléments d’une colonne sont nuls sur et sous la diagonale), on continue
comme si de rien était...
gauss_redi(M):={ local pivo,j,k,nl,nc,temp,l,n,a; nl:=nrows(M); nc:=ncols(M); n:=min(nl,nc-1); //on met des 0 sous la diagonale du carre n*n for (j:=0;j<n;j++) { //choix du pivot mis ds pivo k:=j; while (M[k,j]==0 and k<nl-1) {k:=k+1;} //on ne fait la suite que si on a pivo!=0 if (M[k,j]!=0) { pivo:=M[k,j]; //echange de la ligne j et de la ligne k for (l:=j;l<nc;l++){ temp:=M[j,l]; M[j,l] := M[k,l]; M[k,l]:=temp; } //fin du choix du pivot qui est M[j,j] for (k:=j+1;k<nl;k++) { a:=M[k,j]; for (l:=j;l<nc;l++){ M[k,l]:=M[k,l]*pivo-M[j,l]*a; } } } } return M; }
On tape :
M0:= [[1,2,3,6],[2,3,1,6],[3,2,1,6]]
gauss_redi(M0)
On obtient :
[[1,2,3,6],[0,-1,-5,-6],[0,0,-12,-12]]
On tape :
M1:= [[1,2,3,4],[0,0,1,2],[0,0,5,1]]
gauss_redi(M1)
On obtient :
[[1,2,3,4],[0,0,1,2],[0,0,5,1]]
On tape :
M2:= [[1,2,3,4],[0,0,1,2],[0,0,5,1],[0,0,3,2],[0,0,-1,1]]
gauss_redi(M2)
On obtient :
[[1,2,3,4],[0,0,1,2],[0,0,5,1],[0,0,0,7],[0,0,0,6]]
c’est à dire :
| = | ⎡ ⎢ ⎢ ⎢ ⎢ ⎢ ⎣ |
| ⎤ ⎥ ⎥ ⎥ ⎥ ⎥ ⎦ |
On cherche un programme valable lorsque A est quelconque :
on veut, dans ce cas, mettre des zéros sous la "fausse diagonale" de A
(ce qu’on appelle "fausse diagonale" c’est la diagonale obtenue en ne tenant
pas compte des colonnes pour lesquelles la recherche du pivot a été
infructueuse : un peu comme si ces colonnes étaient rejetées à la fin
de la matrice).
Donc, dans ce programme, si on ne trouve pas de pivot pour la colonne d’indice
jc (i. e. si tous les éléments de la colonne jc sont nuls sur et sous
la diagonale), on continue en cherchant, pour la colonne suivante (celle
d’indice jc+1), un pivot à partir de l’élément situé à la ligne
d’indice jc (et non comme précédemment à partir de jc+1),
pour mettre sur la colonne jc+1, des zéros sur les lignes jc+1,...,nl−1.
On est donc obligé, d’avoir 2 indices jl et jc pour repérer les
indices de la "fausse diagonale".
gauss_red(M):={ local pivo,jc,jl,k,nl,nc,temp,l,a; nl:=nrows(M); nc:=ncols(M); //on met des 0 sous la fausse diagonale d'indice jl,jc jc:=0; jl:=0; //on traite chaque colonne (indice jc) while (jc<nc-1 and jl<nl-1) { //choix du pivot que l'on veut mettre en M[jl,jc] k:=jl; while (M[k,jc]==0 and k<nl-1) {k:=k+1;} //on ne fait la suite que si M[k,jc](=pivo)!=0 if (M[k,jc]!=0) { pivo:=M[k,jc]; //echange de la ligne jl et de la ligne k for (l:=jc;l<nc;l++){ temp:=M[jl,l]; M[jl,l] := M[k,l]; M[k,l]:=temp; } //fin du choix du pivot qui est M[jl,jc] //on met des 0 sous la fausse diagonale de //la colonne jc for (k:=jl+1;k<nl;k++) { a:=M[k,jc]; for (l:=jc;l<nc;l++){ M[k,l]:=M[k,l]*pivo-M[jl,l]*a; } } //on a 1 pivot,l'indice-ligne de //la fausse diag augmente de 1 jl:=jl+1; }//fin du if (M[k,jc]!=0) //colonne suivante,l'indice-colonne de //la fausse diag augmente de 1 jc:=jc+1; }//fin du while return M; }
On tape :
M0:= [[1,2,3,6],[2,3,1,6],[3,2,1,6]]
gauss_red(M0)
On obtient :
[[1,2,3,6],[0,-1,-5,-6],[0,0,-12,-12]]
On tape :
M1:= [[1,2,3,4],[0,0,1,2],[0,0,5,1]]
gauss_red(M1)
On obtient :
[[1,2,3,4],[0,0,1,2],[0,0,0,-9]]
On tape :
M2:= [[1,2,3,4],[0,0,1,2],[0,0,5,1],[0,0,3,2],[0,0,-1,1]]
gauss_red(M2)
On obtient :
[[1,2,3,4],[0,0,1,2],[0,0,0,-9],[0,0,0,-4],[0,0,0,3]]
| = | ⎡ ⎢ ⎢ ⎢ ⎢ ⎢ ⎣ |
| ⎤ ⎥ ⎥ ⎥ ⎥ ⎥ ⎦ |
Étant donnée un système d’équations, on cherche à le remplacer
par un système diagonale équivalent.
À un système d’équations AX=b, on associe la matrice M formée
de A, bordée par b comme dernière colonne.
La fonction gaussjordan_redi transforme M par la méthode de
Gauss-Jordan.
On cherche dans chaque colonne j,(j=0..nc−2) à partir de la diagonale,
ce qui va faire office de pivot : dans le programme ci-dessous on choisit le
premier élément non nul, puis par un échange de lignes, on met le pivot
sur la diagonale (pivo=M[j,j]). Il ne reste plus qu’à former, pour chaque
ligne k (k≠ j) et pour a=M[k,j], la combinaison :
pivo*lignek−a*lignej (et ainsi M[k,j] devient nul).
On écrit pour réaliser cette combinaison :
lorsque k<j
a:=M[k,j];
for (l:=0;l<j;l++){
M[k,l]:=M[k,l]*pivo-M[j,l]*a;}
et lorsque k>j
a:=M[k,j];
for (l:=j;l<nc;l++){
M[k,l]:=M[k,l]*pivo-M[j,l]*a;}
On remarquera que l part soit de 0 soit de j car pour l<j, on a
M[k,l]=0 seulement si k>j.
Si on ne trouve pas de pivot, on continue comme si de rien n’était : on
obtiendra donc des zéros au dessus de la diagonale que si on
a trouvé un pivot pour chaque colonne.
gaussjordan_redi(M):={ local pivo,j,k,nl,nc,temp,l,n,a; nl:=nrows(M); nc:=ncols(M); n:=min(nl,nc-1); //on met 0 sous et au dessus de la diag du carre n*n for (j:=0;j<n;j++) { //choix du pivot k:=j; while (M[k,j]==0 and k<nl-1) {k:=k+1;} //on ne fait la suite que si on a pivo!=0 if (M[k,j]!=0) { pivo:=M[k,j]; //echange de la ligne j et de la ligne k for (l:=j;l<nc;l++){ temp:=M[j,l]; M[j,l] := M[k,l]; M[k,l]:=temp; } //fin du choix du pivot qui est M[j,j] // on met des zeros au dessus de la diag for (k:=0;k<j;k++) { a:=M[k,j]; for (l:=0;l<nc;l++){ M[k,l]:=M[k,l]*pivo-M[j,l]*a; } } // on met des zeros au dessous de la diag for (k:=j+1;k<nl;k++) { a:=M[k,j]; for (l:=j;l<nc;l++){ M[k,l]:=M[k,l]*pivo-M[j,l]*a; } } } } return M; }
De la même façon que pour la mèthode de Gauss, on va mettre des zéros sous la "fausse diagonale" et au dessus de cette "fausse diagonale" (on ne pourra pas bien sûr, mettre des zéros au dessus de cette "fausse diagonale", pour les colonnes sans pivot!!)
gaussjordan_red(M):={ local pivo,jc,jl,k,nl,nc,temp,l,a; nl:=nrows(M); nc:=ncols(M); //on met des 0 sous la fausse diagonale jc:=0; jl:=0; //on doit traiter toutes les colonnes sauf la derniere //on doit traiter toutes les lignes while (jc<nc-1 and jl<nl) { //choix du pivot que l'on veut mettre en M[jl,jc] k:=jl; while (M[k,jc]==0 and k<nl-1) {k:=k+1;} //on ne fait la suite que si on a pivo!=0 if (M[k,jc]!=0) { pivo:=M[k,jc]; //echange de la ligne jl et de la ligne k for (l:=jc;l<nc;l++){ temp:=M[jl,l]; M[jl,l] := M[k,l]; M[k,l]:=temp; } //fin du choix du pivot qui est M[jl,jc] //on met des 0 au dessus la fausse diagonale de //la colonne jc for (k:=0;k<jl;k++) { a:=M[k,jc]; for (l:=0;l<nc;l++){ M[k,l]:=M[k,l]*pivo-M[jl,l]*a; } } //on met 0 sous la fausse diag de la colonne jc for (k:=jl+1;k<nl;k++) { a:=M[k,jc]; for (l:=jc;l<nc;l++){ M[k,l]:=M[k,l]*pivo-M[jl,l]*a; } } //on a un pivot donc le numero de //la ligne de la fausse diag augmente de 1 jl:=jl+1; } //ds tous les cas, le numero de //la colonne de la fausse diag augmente de 1 jc:=jc+1; } return M; }
On tape :
M0:= [[1,2,3,6],[2,3,1,6],[3,2,1,6]]
gaussjordan_red(M0)
On obtient :
[[12,0,0,12],[0,12,0,12],[0,0,-12,-12]]
On tape :
M1:= [[1,2,3,4],[0,0,1,2],[0,0,5,1]]
gaussjordan_red(M1)
On obtient :
[[1,2,0,-2],[0,0,1,2],[0,0,0,-9]]
On tape :
M2:= [[1,2,3,4],[0,0,1,2],[0,0,5,1],[0,0,3,2],[0,0,-1,1]]
gaussjordan_red(M2)
On obtient :
[[1,2,0,-2],[0,0,1,2],[0,0,0,-9],[0,0,0,-4],[0,0,0,3]]
| = | ⎡ ⎢ ⎢ ⎢ ⎢ ⎢ ⎣ |
| ⎤ ⎥ ⎥ ⎥ ⎥ ⎥ ⎦ |
On peut bien sûr modifier la recherche du pivot.
Pour les méthodes numériques il est recommandé de normaliser les
équations (on divise chaque ligne k par maxj|ak,j|) et de choisir
le pivot qui a la plus grande valeur absolue.
En calcul formel, on prend l’expression exacte la plus simple possible.
Ici on normalise les équations à chaque étape et on choisit
le pivot qui a la plus grande valeur absolue : c’est ce que font, avec ce choix du pivot les programmes (cf le répertoire exemples),
gauss_reducni (analogue à gauss_redi),
gauss_reducn (analogue à gauss_red)),
gaussjordan_reducni (analogue à gaussjordan_redi) et
gaussjordan_reducn (analogue à gaussjordan_red) :
gaussjordan_reducn(M):={ local pivo,j,jc,jl,k,nl,nc,temp,l,a,piv,kpiv,maxi; nl:=nrows(M); nc:=ncols(M); //on met des 0 sous la fausse diagonale jc:=0; jl:=0; while (jc<nc-1 and jl<nl) { //on normalise les lignes for (jj:=0;jj<nl;jj++) { maxi:=max(abs(seq(M[jj,kk], kk=0..nc-1))); for (kk:=0;kk<nc;kk++) { M[jj,kk]:=M[jj,kk]/maxi; } } //choix du pivot que l'on veut mettre en M[jl,jc] kpiv:=jl; piv:=abs(M[kpiv,jc]); for (k:=jl+1;k<nl;k++){ if (abs(M[k,jc])>piv) {piv:=abs(M[k,jc]);kpiv:=k;} } //on ne fait la suite que si on a piv!=0 if (piv!=0) { pivo:=M[kpiv,jc]; k:=kpiv; //echange de la ligne jl et de la ligne k for (l:=jc;l<nc;l++){ temp:=M[jl,l]; M[jl,l] := M[k,l]; M[k,l]:=temp; } //fin du choix du pivot qui est M[jl,jc] //on met des 0 au dessus la fausse diagonale //de la colonne jc for (k:=0;k<jl;k++) { a:=M[k,jc]; for (l:=0;l<nc;l++){ M[k,l]:=M[k,l]*pivo-M[jl,l]*a; } } //on met des 0 sous la fausse dia de la colonne jc for (k:=jl+1;k<nl;k++) { a:=M[k,jc]; for (l:=jc;l<nc;l++){ M[k,l]:=M[k,l]*pivo-M[jl,l]*a; } } //on a un pivot donc,le numero de //la ligne de la fausse diag augmente de 1 jl:=jl+1; } //ds tous les cas,le numero de //la colonne de la fausse diag augmente de 1 jc:=jc+1; } return M; }
On tape :
M0:= [[1,2,3,6],[2,3,1,6],[3,2,1,6]]
gaussjordan_reducn(M0)
On obtient :
[[5/6,0,0,5/6],[0,5/6,0,5/6],[0,0,1,1]]
On tape :
M1:= [[1,2,3,4],[0,0,1,2],[0,0,5,1]]
gaussjordan_reducn(M1)
On obtient :
[[1/4,1/2,0,17/20],[0,0,1,1/5],[0,0,0,9/10]]
On tape :
M2:=[[1,2,3,4],[0,0,1,2],[0,0,5,1],[0,0,3,2],[0,0,-1,1]]
gaussjordan_reducn(M2)
On obtient :
[[1/4,1/2,0,17/20],[0,0,1,1/5],
[0,0,0,9/10],[0,0,0,7/15],[0,0,0,6/5]]
On a donc :
| = | ⎡ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎢ ⎣ |
| ⎤ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎥ ⎦ |
Soit une application linéaire f de ℝn dans ℝp,
de matrice A dans la base canonique de ℝn et de ℝp.
Trouver le noyau de f revient à résoudre f(X)=0 ou encore
à résoudre AX=0 pour X∈ℝn.
Pour cela, on va utiliser la méthode de Gauss-Jordan en rajoutant des lignes
de 0 aux endroits où l’on n’a pas trouvé de pivot de façon à ce que
les pivots suivants se trouve sur la diagonale (et non sur la
"fausse diagonale").
Plus précisément :
- quand on a trouvé un pivot pour une colonne, à l’aide de la méthode
habituelle de réduction de Gauss-Jordan, on met sur cette colonne :
1 sur la diagonale et
0 de part et d’autre du 1.
- quand on n’a pas trouvé de pivot pour la colonne j, on rajoute
une ligne de 0 : la ligne j devient une ligne de 0 et les autres lignes
sont décalées.
- on rajoute éventuellement à la fin, des lignes de 0, pour traiter
toutes les colonnes de A et avoir une matrice carrée.
- on enlève éventuellement à la fin, des lignes de 0, pour avoir
une matrice carrée.
Remarque : on ne change pas le nombre de colonnes.
Ainsi, si la colonne Cj a un 0 sur sa diagonale, si ej est le j-ième
vecteur de la base canonique de ℝn, alors Nj=Cj−ej est
un vecteur du noyau. En effet, on a A(ej)=Cj; si on a Cj=a1e1+....+aj−1ej−1, pour k<j, et pour ak!=0, on a A(ek)=ek c’est à dire
A(Cj)=Cj=A(ej), soit A(Cj−ej)=A(Nj)=0.
Voici le programme correspondant en conservant la variable jl afin de
faciliter la lisibilité du programme :
gaussjordan_noyau(M):={ local pivo,jc,jl,k,j,nl,nc,temp,l,a,noyau; nl:=nrows(M); nc:=ncols(M); //on met des 0 sous la diagonale jc:=0; jl:=0; // on traite toutes les colonnes while (jc<nc and jl<nl) { //choix du pivot que l'on veut mettre en M[jl,jc] k:=jl; while (M[k,jc]==0 and k<nl-1) {k:=k+1;} //on ne fait la suite que si on a pivo!=0 if (M[k,jc]!=0) { pivo:=M[k,jc]; //echange de la ligne jl et de la ligne k for (l:=jc;l<nc;l++){ temp:=M[jl,l]; M[jl,l] := M[k,l]; M[k,l]:=temp; } //fin du choix du pivot qui est M[jl,jc] //on met 1 sur la diagonale de la colonne jc for (l:=0;l<nc;l++) { M[jl,l]:=M[jl,l]/pivo; } //on met des 0 au dessus de la diagonale // de la colonne jc for (k:=0;k<jl;k++) { a:=M[k,jc]; for (l:=0;l<nc;l++){ M[k,l]:=M[k,l]-M[jl,l]*a; } } //on met des 0 sous la diag de la colonne jc for (k:=jl+1;k<nl;k++) { a:=M[k,jc]; for (l:=jc;l<nc;l++){ M[k,l]:=M[k,l]-M[jl,l]*a; } } } else{ //on ajoute une ligne de 0 si ce n'est pas le dernier 0 if (jl<nc-1){ for (j:=nl;j>jl;j--){ M[j]:=M[j-1]; } M[jl]:=makelist(0,1,nc); nl:=nl+1; } } //ds tous les cas,le numero de colonne et //le numero de ligne augmente de 1 jc:=jc+1; jl:=jl+1; //il faut faire toutes les colonnes if (jl==nl and jl<nc) { M[nl]:=makelist(0,1,nc);nl:=nl+1;} } noyau:=[]; //on enleve les lignes en trop pour avoir //une matrice carree de dim nc //on retranche la matrice identite M:=M[0..nc-1]-idn(nc); for(j:=0;j<nc;j++){ if (M[j,j]==-1) {noyau:=append(noyau,M[0..nc-1,j]);} } return noyau; }
Remarque
On peut écrire le même programme en supprimant la variable
jl puisque jl=jc (on met jc à la place de jl et on supprime jl:=0
et jl:=jl+1).
On met ce programme
dans un niveau éditeur de programmes (que l’on ouvre avec Alt+p), puis
on le teste et on le valide avec OK.
On tape :
gaussjordan_noyau([[1,2,3],[1,3,6],[2,5,9]])
On obtient :
[[-3,3,-1]]
On tape :
gaussjordan_noyau([[1,2,3,4],[1,3,6,6],[2,5,9,10]])
On obtient :
[[-3,3,-1,0],[0,2,0,-1]]
On associe à un système d’équations linéaires, une matrice A
constituèe de la matrice du système augmentèe d’une colonne formée
par l’opposé du second membre.
Par exemple au système :
[x+y=4,x−y=2] d’inconnues [x,y] on associe la matrice :
A=[[1,1,−4],[1,−1,−2]].
Puis on réduit avec la méthode de Gauss-Jordan la matrice A pour obtenir
une matrice B. Pour chaque ligne de B :
- si il n’y a que des zéro on regarde la ligne suivante,
- si il y a des zéro sauf en dernière position, il n’y a pas de solution,
- dans les autres cas on obtient la valeur de la variable de même indice
que le premier élément non nul rencontré sur la ligne,
- les valeurs arbitraires correspondent aux zéros de la diagonale de B et
en général il y a des valeurs non nulles au dessus de ces zéros,
c’est pourquoi il faut initialisé la solution au vecteur des variables.
Voici le programme de résolution d’un système linéaire :
//Veq vecteur des equations //v vecteur de variables //renvoie le vecteur solution linsolv(Veq,v):={ local A,B,j,k,l,deq,d,res,ll,rep; d:=size(v); deq:=size(Veq); //A est la matrice du systeme +le 2nd membre A:=syst2mat(Veq,v); //B matrice reduite de Gauss-jordan B:=rref(A); res:=v; //ll ligne l de B ll:=makelist(0,0,d); for (l:=0; l<deq;l++){ for (k:=0;k<d+1;k++){ ll[k]:=B[l][k]; } j:=l; while (ll[j]==0 && j<d){ j:=j+1; } //si (j==d and ll[d]==0) //ll=ligne de zeros on ne fait rien if (j==d and ll[d]!=0){ // pas de sol return []; } else {//la sol res[j] vaut rep/ll[j] if (j<d) { rep:=-ll[d]; for (k:=j+1;k<d;k++) { rep:=rep-ll[k]*v[k]; } res[j]:=rep/ll[j]; } } } return res; }
On transforme la résolution de MX=b en MX−b=AY=0 où A est la matrice
constituèe de la matrice M du système augmentèe d’une
colonne formée par l’opposé du second membre b.
Y est donc un vecteur du noyau de A ayant comme dernière composante 1.
D’après l’algorithme de recherche du noyau, seul le dernier vecteur a comme
dernière composante -1. Si -ker(A) renvoie n vecteurs, la solution
Y est donc une combinaison arbitraire des n−1 premiers vecteurs du noyau
plus le n-ième vecteur.
Voici le programme de résolution de AX=b :
//M*res=b res renvoie le vecteur solution //M:=[[1,1],[1,-1]]; b:=[4,2] //M:=[[1,1,1],[1,1,1],[1,1,1]];b:=[0,0,0] //M:=[[1,2,1],[1,2,5],[1,2,1]];b:=[0,1,0] linsolvm(M,b):={ local A,B,N,n,k,d,res; d:=ncols(M); //A est la matrice du systeme +le 2nd membre A:=border(M,-b); //N contient une base du noyau N:=-ker(A); n:=size(N); //res a d+1 composante (la derniere=1) res:=makelist(0,0,d); //C_(k) designe les constantes arbitraires for (k:=0;k<n-1;k++){ res:=res+N[k]*C_(k); } res:=res+N[n-1]; res:=suppress(res,d); return res; }
C’est l’interprétation matricielle de la méthode de Gauss.
Si A est une matrice carrée d’ordre n, il existe une matrice L
triangulaire supérieure, une matrice L
triangulaire inférieure, et une matrice P de permutation telles que :
P*A=L*U.
Supposons tout d’abord que l’on peut faire la méthode de Gauss sans
échanger des lignes. Mettre des zéros sous la diagonale de la 1-ière
colonne (d’ndice 0) de A, reviens à multiplier A par la matrice E0
qui a des 1 sur sa diagonale, comme première colonne :
[1,−A[1,0]/A[0,0], ...−A[n−1,0]/A[0,0]] et des zéros ailleurs.
Puis si A1=E1*A, mettre des zéros sous la diagonale de la 2-ière
colonne (d’ndice 1) de A1, reviens à multiplier A1 par la matrice E1
qui a des 1 sur sa diagonale, comme deuxième colonne :
[0,1,−A1[2,1]/A[1,1], ...−A[n−1,1]/A[1,1]] et des zéros ailleurs.
On continue ainsi jusqu’à mettre des zéros sous la diagonale de la colonne
d’indice n−2, et à la fin la matrice U=En−2*...*E1*E0*A est
triangulaire supérieure et on a L=inv(En−2*...*E1*E0).
Le calcul de inv(L) est simple car on a :
- inv(E0) des 1 sur sa diagonale, comme colonne d’indice 0
[1,+A[1,0]/A[0,0], ...+A[n−1,0]/A[0,0]] et des zéros ailleurs de même
inv(Ek) est obtenue à partir de Ek "en changeant les moins en plus".
- la colonne d’indice k de inv(E0)*inv(E1)...inv(En−2) est égale à
la colonne d’indice k de Ek.
Lorsqu’il y a à faire une permutation de lignes, il faut répercuter cette
permutation sur L et sur U : pour faciliter la programmation on va
conserver les valeurs de L et de U dans une seule matrice R que l’on
séparera à la fin : U sera la partie supérieure et la diagonale de R
L sera la partie inférieure de R, avec des 1 sur sa diagonale.
Voici le programme de séparation :
splitmat(R):={ local L,U,n,k,j; n:=size(R); L:=idn(n); U:=makemat(0,n,n); for (k:=0;k<n;k++){ for (j:=k;j<n;j++){ U[k,j]:= R[k,j]; } } for (k:=1;k<n;k++){ for (j:=0;j<k;j++){ L[k,j]:= R[k,j]; } } return (L,U); };
Le programme ci-dessous, decomplu(A), renvoie la permutation p que l’on
a fait sur les lignes, L et U et on a:
P*A=L*U.
Voici le programme de décomposition LU qui utilise splitmat ci-dessus :
//A:=[[5,2,1],[5,2,2],[-4,2,1]] //A:=[[5,2,1],[5,-6,2],[-4,2,1]] // utilise splitmat decomplu(A):={ local B,R,L,U,n,j,k,l,temp,p; n:=size(A); p:=seq(k,k,0,n-1); R:=makemat(0,n,n); B:=A; l:=0; //on traite toutes les colonnes while (l<n-1) { if (A[l,l]!=0){ //pas de permutations //on recopie dans R la ligne l de A //a partir de la diagonale for (j:=l;j<n;j++){R[l,j]:=A[l,j];} //on met des zeros sous la diagonale //dans la colonne l for (k:=l+1;k<n;k++){ for (j:=l+1;j<n;j++){ A[k,j]:=A[k,j]-A[l,j]*A[k,l]/A[l,l]; R[k,j]:=A[k,j]; } R[k,l]:=A[k,l]/A[l,l]; A[k,l]:=0; } l:=l+1; } else { k:=l; while ((k<n-1) and (A[k,l]==0)) { k:=k+1; } //si (A[k,l]==0) A est non inversible, //on passe a la colonne suivante if (A[k,l]==0) { l:=l+1; } else { //A[k,l]!=0) on echange la ligne l et k ds A et R for (j:=l;j<n;j++){ temp:=A[k,j]; A[k,j]:=A[l,j]; A[l,j]:=temp; }; for (j:=0;j<n;j++){ temp:=R[k,j]; R[k,j]:=R[l,j]; R[l,j]:=temp; } //on note cet echange dans p temp:=p[k]; p[k]:=p[l]; p[l]:=temp; }//fin du if (A[k,l]==0) }//fin du if (A[l,l]!=0) }//fin du while L,U:=splitmat(R); return(p,L,U); };
On tape :
A:=[[5,2,1],[5,2,2],[-4,2,1]]
decomplu(A)
On obtient :
[0,2,1],[[1,0,0],[-4/5,1,0],[1,0,1]],
[[5,2,1],[0,18/5,9/5],[0,0,1]]
On verifie, on tape (car permu2mat(p)=P=inv(P)) :
[[1,0,0],[0,0,1],[0,1,0]]*[[1,0,0],[-4/5,1,0],[1,0,1]]*
[[5,2,1],[0,18/5,9/5],[0,0,1]]
On obtient bien [[5,2,1],[5,2,2],[-4,2,1]]
On tape :
B:=[[5,2,1],[5,-6,2],[-4,2,1]]
decomplu(B)
On obtient :
[0,1,2],,[[1,0,0],[1,1,0],[-4/5,-9/20,1]],
[[5,2,1],[0,-8,1],[0,0,9/4]]
On verifie, on tape :
[[1,0,0],[1,1,0],[-4/5,-9/20,1]]*[[5,2,1],[0,-8,1],[0,0,9/4]]
On obtient bien [[5,2,1],[5,-6,2],[-4,2,1]]
Lorsque la matrice A est la matrice associée à une forme bilinéaire
définie positive, on lui associe la matrice symétrique
B=1/2*(A+tran(A)) qui est la matrice de la forme quatratique
associée.
Avec ses hypothèses, il existe une matrice triangulaire inférieure unique
C telle que B=C*tran(C).
Pour déterminer C on présente ici deux méthodes :
Si on décompose B selon la méthode LU, on n’est pas obligé de faire
des échanges de lignes car les sous-matrices principales Bk d’ordre k
(obtenues en prenant les k premières lignes et colonnes de B) sont des
matrices inversibles car ce sont des matrices de formes définies positives.
On a donc :
p,L,U:=decomplu(B) avec p=[0,1..n-1] si A est d’ordre
n.
Posons D la matrice diagonale ayant comme diagonale la racine carrée de
la diagonale de U.
On a alors C=L*D et tran(C)=inv(D)*U.
Pour le montrer on utilise le théorème :
Si B=C*F avec
B symétrique, C triangulaire inférieure et F triangulaire
supérieure de même diagonale que C alors F=tran(C).
En effet on a :
B=tran(B) donc C*F=tran(C*F)=tran(F)*tran(C).
On en déduit l’égalité des 2 matrices :
inv(tran(F))*C=tran(C)*inv(F)
la première est triangulaire inférieure, et la deuxième est triangulaire
supérieure donc ces matrices sont diagonales et leur diagonale n’a que des 1
ces 2 matrices sont donc égales à la matrice unité.
On peut aussi déterminer C par identification en utilisant
l’égalité B=C*tran(C) et C triangulaire inférieure.
On trouve pour tout entier j compris entre 0 et n−1 :
- pour la diagonale :
(C[j,j])2=B[j,j]-∑k=0j-1(C[j,k])2
- pour les termes subdiagonaux c’est à dire pour j+1 ≤ l ≤ n−1 :
C[l,j]=1/C[j,j]*(B[l,j]-∑k=0j-1C[l,k]*C[j,k])
On peut donc calculer les coefficients de C par colonnes, en effet, pour
avoir la colonne j :
- on calcule le terme diagonal C[j,j] qui fait intervenir les termes de
la ligne j des colonnes précédentes (avec un test vérifiant
que le terme B[j,j]-∑k=0j-1(C[j,k])2 est positif), puis,
- on calcule les termes subdiagonaux C[l,j] pour j+1 ≤ l ≤ n−1
qui font intervenir les termes des colonnes précédentes des lignes
précédentes.
Mais cet algorithme n’est pas très bon car les termes de la matrice C
sont obtenus à chaque étape avec une multiplication ou une division de
racine carrée : il serait préférable d’introduire les racines carrées
qu’à la fin du calcul comme dans la méthode LU. On va donc calculer une
matrice CC (sans utiliser de racines carrées) et une matrice diagonale
D (qui aura des racines carrées sur sa diagonale)
de tel sorte que C=CC*D (la colonne k de CC*D est la colonne k
de CC multiplié par D[k,k]).
Par exemple : C[0,0]* C[0,0]= B[0,0] on pose :
CC[0,0]= B[0,0]=a et D[0,0]=1/sqrt(a) ainsi
C[0,0]= CC[0,0]/sqrt(a)=sqrt(a) et on a bien :
C[0,0]* C[0,0]=a=B[0,0]
On aura donc C[l,0]=CC[l,0]/sqrt(a) c’est à dire :
CC[l,0]=B[l,0] pour 0≤ l<n
Puis :
C[1,1]*C[1,1]=B[1,1]-C[1,0]*C[1,0]=
B[1,1]-CC[1,0]*CC[1,0]/a=b
on pose :
CC[1,1]= B[1,1]-CC[1,0]*CC[1,0]/a=b et
D[1,1]=1/sqrt(b)
On aura donc pour 2≤ l<n ;
C[l,1]=CC[l,1]/sqrt(b)=1/sqrt(b)*(B[l,1]-C[l,0]*C[1,0])
c’est à dire pour 2≤ l<n :
CC[l,1]=B[l,1]-C[l,0]*C[1,0]=B[l,1]-CC[l,0]*CC[1,0]/a
etc.....
Les formules de récurences pour le calcul de CC sont :
- pour la diagonale :
CC[j,j]=B[j,j]-∑k=0j-1(CC[j,k])2/CC[k,k]
- pour les termes subdiagonaux c’est à dire pour j+1 ≤ l ≤ n−1 :
CC[l,j]=B[l,j]-∑k=0j-1CC[l,k]*CC[j,k]/CC[k,k]
avec D[j,j]=1/sqrt(CC[j,j]).
//utilise decomplu et splitmat ci-dessus //A:=[[1,0,-1],[0,2,4],[-1,4,11]] //A:=[[1,1,1],[1,2,4],[1,4,11]] //A:=[[1,0,-2],[0,2,6],[0,2,11]] //A:=[[1,-2,4],[-2,13,-11],[4,-11,21]] //A:=[[-1,-2,4],[-2,13,-11],[4,-11,21]] (pas def pos) //A:=[[24,66,13],[66,230,-11],[13,-11,210]] choles(A):={ local j,n,p,L,U,D,p0; n:=size(A); A:=1/2*(A+tran(A)); (p,L,U):=decomplu(A); p0:=makelist(x->x,0,n-1); if (p!=p0) {return "pas definie positive ";} D:=makemat(0,n,n); for (j:=0;j<n;j++) { //if (U[j,j]<0) {return "pas def positive";} D[j,j]:=sqrt(U[j,j]); } return normal(L*D); }
//A:=[[1,0,-1],[0,2,4],[-1,4,11]] //A:=[[1,1,1],[1,2,4],[1,4,11]] //A:=[[1,0,-2],[0,2,6],[0,2,11]] //A:=[[1,-2,4],[-2,13,-11],[4,-11,21]] //A:=[[-1,-2,4],[-2,13,-11],[4,-11,21]] (pas def pos) //A:=[[24,66,13],[66,230,-11],[13,-11,210]] cholesi(A):={ local j,n,l,k,C,c2,s; n:=size(A); A:=1/2*(A+tran(A)); C:=makemat(0,n,n);; for (j:=0;j<n;j++) { s:=0; for (k:=0;k<j;k++) { s:=s+(C[j,k])^2; } c2:=A[j,j]-s; if (c2<=0) {return "pas definie positive ";} C[j,j]:=normal(sqrt(c2)); for (l:=j+1;l<n;l++) { s:=0; for (k:=0;k<j;k++) { s:=s+C[l,k]*C[j,k]; } C[l,j]:=normal(1/sqrt(c2)*(A[l,j]-s)); } } return C; }
Ce programme n’est pas très bon car les termes de la matrice C sont
obtenus avec une multiplication ou une division de racine carrée...
On se pourra se reporter ci-dessous pour avoir le programme choleski optimisé.
//A:=[[1,0,-1],[0,2,4],[-1,4,11]] //A:=[[1,1,1],[1,2,4],[1,4,11]] //A:=[[1,0,-2],[0,2,6],[0,2,11]] //A:=[[1,-2,4],[-2,13,-11],[4,-11,21]] //A:=[[-1,-2,4],[-2,13,-11],[4,-11,21]] (pas def pos) //A:=[[24,66,13],[66,230,-11],[13,-11,210]] choleski(A):={ local j,n,l,k,CC,D,c2,s; n:=size(A); A:=1/2*(A+tran(A)); CC:=makemat(0,n,n); D:=makemat(0,n,n); for (j:=0;j<n;j++) { s:=0; for (k:=0;k<j;k++) { s:=s+(CC[j,k])^2/CC[k,k]; } c2:=normal(A[j,j]-s); //if (c2<=0) {return "pas definie positive ";} CC[j,j]:=c2; D[j,j]:=normal(1/sqrt(c2)); for (l:=j+1;l<n;l++) { s:=0; for (k:=0;k<j;k++) { s:=s+CC[l,k]*CC[j,k]/CC[k,k]; } CC[l,j]:=normal(A[l,j]-s); } } return normal(CC*D); }
Avec cette méthode, pour obtenir les coefficients diagonaux ont utilise la
même relation de récurrence que pour les autres coefficients. De plus on
peut effectuer directement et facilement la multiplication par D sans
avoir à définir D en multipliant les colonnes de CC par la
même valeur 1/sqrt(c) avec c=CC[k,k]...
donc on écrit :
choleskii(A):={ local j,n,l,k,CC,c,s; n:=size(A); A:=1/2*(A+tran(A)); CC:=makemat(0,n,n); for (j:=0;j<n;j:=j+1) { for (l:=j;l<n;l++) { s:=0; for (k:=0;k<j;k++) { //if (CC[k,k]<=0) {return "pas definie positive ";} if (CC[k,k]==0) {return "pas definie";} s:=s+CC[l,k]*CC[j,k]/CC[k,k]; } CC[l,j]:=A[l,j]-s; } } for (k:=0;k<n;k++) { c:=CC[k,k]; for (j:=k;j<n;j++) { CC[j,k]:=normal(CC[j,k]/sqrt(c)); } } return CC; }
Avec la traduction pour avoir une fonction interne à Xcas :
cholesky(_args)={ gen args=(_args+mtran(_args))/2; matrice &A=*args._VECTptr; int n=A.size(),j,k,l; vector<vecteur> C(n,vecteur(n)), D(n,vecteur(n)); for (j=0;j<n;j++) { gen s; for (k=0;k<j;k++) s=s+pow(C[j][k],2)/C[k][k]; gen c2=A[j][j]-s; if (is_strictly_positive(-c2)) setsizeerr("Not a positive define matrice"); C[j][j]=c2; D[j][j]=normal(1/sqrt(c2)); for (l=j+1;l<n;l++) { s=0; for (k=0;k<j;k++) s=s+C[l][k]*C[j][k]/C[k][k]; C[l][j]=A[l][j]-s; } } matrice Cmat,Dmat; vector_of_vecteur2matrice(C,Cmat); vector_of_vecteur2matrice(D,Dmat); return Cmat*Dmat; }
Une matrice de Hessenberg est une matrice qui a des zéros sous
la "deuxième diagonale inférieure".
Soit A une matrice. On va chercher B une matrice de Hessenberg semblable
à A. Pour cela on va mettre des zéros sous cette diagonale
en utilisant la méthode de Gauss mais en prenant les pivots sur la
"deuxième diagonale inférieure" encore appelée "sous-diagonale": cela
revient à multiplier A par
Q=R−1 et cela permet de conserver les zéros lorsque l’on multiplie
à chaque étape le résultat par R pour obtenir une matrice semblable
à A. Si on est obligé de faire un échange de lignes (correspondant à
la multiplication à droite par E) il faudra faire aussi un échange de
colonnes (correspondant à la multiplication à gauche par E.
Par exemple si on a :
A:=[
a00 | a01 | a02 | a03 | a04 |
a10 | a11 | a12 | a13 | a14 |
a20 | a21 | a22 | a23 | a24 |
a30 | a31 | a32 | a33 | a34 |
a40 | a41 | a42 | a43 | a44 |
]
Pour mettre des zéros dans la première colonne en dessous de a10, on
va multiplier A à gauche par Q et à droite par R=Q−1 avec si on
suppose a10!=0 c’est à dire si on peut choisir comme pivot a10 :
Q:=[
1 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 0 |
0 | −a20/a10 | 1 | 0 | 0 |
0 | −a30/a10 | 0 | 1 | 0 |
0 | −a40/a10 | 0 | 0 | 1 |
]
R:=[
1 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 0 |
0 | +a20/a10 | 1 | 0 | 0 |
0 | +a30/a10 | 0 | 1 | 0 |
0 | +a40/a10 | 0 | 0 | 1 |
]
On tape alors :
A:=[[a00,a01,a02,a03,a04],[a10,a11,a12,a13,a14],
[a20,a21,a22,a23,a24],[a30,a31,a32,a33,a34],[a40,a41,a42,a43,a44]]
Q:=[[1,0,0,0,0],[0,1,0,0,0],[0,(-a20)/a10,1,0,0],
[0,(-a30)/a10,0,1,0],[0,(-a40)/a10,0,0,1]]
R:=[[1,0,0,0,0],[0,1,0,0,0],[0,(a20)/a10,1,0,0],
[0,(a30)/a10,0,1,0],[0,(a40)/a10,0,0,1]]
On obtient la matrice B1:
B1=Q*A*R=R−1*A*R=[
a00 | ... | a02 | a03 | a04 |
a10 | ... | a12 | a13 | a14 |
0 | ... | ... | ... | ... |
0 | ... | ... | ... | ... |
0 | ... | ... | ... | ... |
]
où les ... sont des expressions des coefficients de A.
On va faire cette transformation successivement sur la deuxième colonne de
la matrice B1 pour obtenir B2 etc...
On appellera B toutes les matrices obtenues successivement.
Si on doit échanger les deux lignes k et j pour avoir un pivot, cela
revient à multiplier à gauche la matrice B par une matrice E égale
à la matrice identité ayant subie l’échange des deux lignes k et j.
Il faudra alors, aussi multiplier à droite la matrice B par E c’est à
dire échanger les deux colonnes k et j de B
//A est mise sous forme de hessenberg B avec //P matrice de passage //p indique si il y a eu une permutation de lignes //a chaque etape la matrice inv(R) // met des zeros sous la "sous diagonale" //B=P^-1AP ex A:=[[5,2,1],[5,2,2],[-4,2,1]] //A:=[[5,2,1,1],[5,2,2,1],[5,2,2,1],[-4,2,1,1]] hessenbg(A):={ local B,R,P,n,j,k,l,temp,p; n:=size(A); P:=idn(n); p:=seq(k,k,0,n-1); B:=A; l:=1; while (l<n) { R:=idn(n); if (B[l,l-1]!=0){ for (k:=l+1;k<n;k++){ R[k,l]:=B[k,l-1]/B[l,l-1]; //on multiplie B a droite par inv(R) for (j:=l;j<n;j++){ B[k,j]:=B[k,j]-B[l,j]*B[k,l-1]/B[l,l-1]; } B[k,l-1]:=0; } //on multiplie B et P a gauche par R B:=B*R; P:=P*R; l:=l+1; } else { k:=l; while ((k<n-1) and (B[k,l-1]==0)) { k:=k+1; } if (B[k,l-1]==0) {l:=l+1;} else{ //B[k,l]!=0) on echange ligne l et k ds B for (j:=l-1;j<n;j++){ temp:=B[k,j]; B[k,j]:=B[l,j]; B[l,j]:=temp; }; //A[k,l]!=0) on echange colonne l et k ds B et P for (j:=0;j<n;j++){ temp:=B[j,k]; B[j,k]:=B[j,l]; B[j,l]:=temp; }; for (j:=0;j<n;j++){ temp:=P[j,k]; P[j,k]:=P[j,l]; P[j,l]:=temp; }; temp:=p[k]; p[k]:=p[l]; p[l]:=temp; } } } return(p,P,B); };
p nous dit les échanges effectués et on a B=P−1*A*P.
Attention !! si A est symétrique, cette transformation détruit la
symétrie et on n’a donc pas une tridiagonalistion d’une matrice symtrisue par cette méthode.
On a le théorème : Pour toute matrice symétrique A d’ordre n, il existe une matrice P, produit de n−2 matrices de rotations, telle que B=tP*A*P soit tridiagonale : c’est la réduction de Givens.
Dans ℝn, on appelle rotation associée à ep, eq,
une rotation d’angle t du plan dirigé par ep, eq où ek désigne
le k+1-ième vecteur de la base canonique de ℝn (la base canonique est e0=[1,0..,0], e1=[0,1,0..,0] etc...).
Si ℝn est rapporté à la base canonique, à cette rotation est
associée une matrice G(n,p,q,t) dont le terme général est :
si k∉{p,q}, gk,k=1
gp,p=gq,q=cos(t)
gp,q=−gq,p=−sin(t)
Voici un matrice de rotation associée à e1, e3 (deuxième et
quatrième vecteur de base) de ℝ5 :
⎡ ⎢ ⎢ ⎢ ⎢ ⎢ ⎣ |
| ⎤ ⎥ ⎥ ⎥ ⎥ ⎥ ⎦ |
et le programme qui construit une telle matrice :
rota(n,p,q,t):={ local G; G:=idn(n); G[p,p]:=cos(t); G[q,q]:=cos(t); G[p,q]:=-sin(t); G[q,p]:=sin(t); return G; }
Soit A une matrice symétrique. On va chercher G1 une matrice de rotation
associée à e1,e2 pour annuler les coefficients 2,0 et 0,2
de tG1*A*G1.
Regardons un exemple :
G1:=[[1,0,0,0,0],[0,cos(t),-sin(t),0,0],[0,sin(t),cos(t),0,0],
[0,0,0,1,0],[0,0,0,0,1]]
A:=[[a,b,c,d,e],[b,f,g,h,j],[c,g,k,l,m],[d,h,l,n,o],[e,j,m,o,r]]
On obtient :
tG1=[
1 | 0 | 0 | 0 | 0 |
0 | cos(t) | sin(t) | 0 | 0 |
0 | −sin(t) | cos(t) | 0 | 0 |
0 | 0 | 0 | 1 | 0 |
0 | 0 | 0 | 0 | 1 |
], A= [
a | b | c | d | e |
b | f | g | h | j |
c | g | k | l | m |
d | h | l | n | o |
e | j | m | o | r |
]
On a :
tG1*A=
[
a | b | c | d | e |
bcos(t)+csin(t) | fcos(t)+gsin(t) | .. | .. | .. |
−bsin(t)+ccos(t) | −fsin(t)+gcos(t) | .. | .. | .. |
d | h | l | n | o |
e | j | m | o | r |
]
On choisit t pour que −bsin(t)+ccos(t)=0 par exemple :
cos(t)=b/√(b2+c2) et sin(t)=c/√(b2+c2).
On a :
G1:=[
1 | 0 | 0 | 0 | 0 |
0 | cos(t) | −sin(t) | 0 | 0 |
0 | sin(t) | cos(t) | 0 | 0 |
0 | 0 | 0 | 1 | 0 |
0 | 0 | 0 | 0 | 1 |
]
et donc puisqu’on a choisit −bsin(t)+ccos(t)=0, on a bien annuler les
coefficients 2,0 et 0,2 de :
tG1*A*G1=
[
a | bcos(t)+csin(t) | −bsin(t)+ccos(t) | d | e |
bcos(t)+csin(t) | .. | .. | .. | .. |
−bsin(t)+ccos(t) | .. | .. | .. | .. |
d | .. | .. | n | o |
e | .. | .. | o | r |
]
Puis on annule les coefficients 0,3 et 3,0 de l la matrice A1
obtenue en formant tG2*A1*G2 avec :
G2:=[
1 | 0 | 0 | 0 | 0 |
0 | cos(t) | 0 | −sin(t) | 0 |
0 | 0 | 1 | 0 | 0 |
0 | sin(t) | 0 | cos(t) | 0 |
0 | 0 | 0 | 0 | 1 |
] en choissant correctement t etc...
//A:=[[1,-1,2,1],[-1,1,-1,1],[2,-1,1,-1],[1,1,-1,-1]] //tran(R)*A*R=B si tridiagivens(A)=R,B //pour annuler le terme 2,0 on multiplie A par TG //TG[1,1]=cos(t)=TG[2,2],TG[1,2]=sin(t)=-TG[2,1], //avec -sin(t)A[1,0]+cos(t)A[2,0]=0 //TG*A multiplie par tran(TG) annule les termes 2,0 et 0,2 //pour annuler le terme q,p (q>p+1) on multiplie A par TG //TG[p+1,p+1]=cos(t)=TG[q,q],TG[p+1,q]=sin(t)=-TG[q,p+1], //avec sin(t)A[p+1,p]=cos(t)A[q,p] //donc sin(t)=A[q,p]/r et cos(t)=A[p+1,p]/r //avec r:=sqrt((A[p+1,p])^2+(A[q,p])^2) tridiagivens(A):={ local n,p,q,r,TG,R,c,s; n:=size(A); R:=idn(n); for (p:=0;p<n-2;p++) { for (q:=p+2;q<n;q++) { r:=sqrt((A[p+1,p])^2+(A[q,p])^2); if (r!=0) { c:=normal(A[p+1,p]/r); s:=normal(A[q,p]/r); TG:=idn(n); TG[p+1,p+1]:=c; TG[q,q]:=c; TG[p+1,q]:=s; TG[q,p+1]:=-s; TG:=normal(TG); A:=normal(TG*A); A:=normal(A*tran(TG)); A:=normal(A); R:=normal(R*tran(TG)); } } } return (R,A); }
On tape :
A:=[[1,-1,2,1],[-1,1,-1,1],[2,-1,1,-1],[1,1,-1,-1]]
tridiagivens(A)
On obtient :
[[1,0,0,0],[0,(-(sqrt(6)))/6, (-(sqrt(4838400000)))/201600, (-(sqrt(2962400000)))/64400], [0,sqrt(6)/3,sqrt(4838400000)/252000, (-(sqrt(26661600000)))/322000], [0,sqrt(6)/6,(-(26*sqrt(210)))/420,12*sqrt(35)/420]], [[1,sqrt(6),0,0], [sqrt(6),1/3,sqrt(275990400000)/266400,0], [0,sqrt(209026944000)/231840,73/105,8*sqrt(6)/35], [0,0,8*sqrt(6)/35,1/-35]]
On tape :
A:=[[1,-1,2,0],[-1,1,-1,1],[2,-1,1,-1],[1,1,-1,-1]]
tridiagivens(A)
On obtient :
[[1,0,0,0],[0,(-(sqrt(6)))/6, (-(sqrt(4838400000)))/201600,(-(sqrt(2962400000)))/64400], [0,sqrt(6)/3,sqrt(4838400000)/252000, (-(sqrt(26661600000)))/322000], [0,sqrt(6)/6,(-(26*sqrt(210)))/420,12*sqrt(35)/420]], [[1,5*sqrt(6)/6,13*sqrt(210)/210,(-(sqrt(35)))/35], [sqrt(6),1/3,sqrt(275990400000)/266400,0], [0,sqrt(209026944000)/231840,73/105,8*sqrt(6)/35], [0,0,8*sqrt(6)/35,1/-35]]
On a le théorème :
Pour toute matrice symétrique A d’ordre n, il existe une matrice P,
produit de n−2 matrice de Householder (donc P−1=tP), telle que
B=tP*A*P soit tridiagonale.
Si A n’est pas symétrique, il existe une matrice P,
produit de n−2 matrice de Householder, telle que B= tP*A*P soit
un matrice de Hessenberg c’est à dire une matrice dont les coefficient
sous-tridiagonaux sonts nuls : c’est la réduction de Householder.
On va dans cette section écrire un programme qui renverra P et B.
Soit un hyperplan défini par son vecteur normal v.
On appelle matrice de Householder, la matrice d’une symétrie orthogonale
hv par rapport à cet hyperplan. On a :
hv(u):=u-2(dot(v,u))/norm(v)^
2*v
et la matrice de Householder associée est :
Hv=idn(n)-2*tran(v)*[v]/(v*v).
On a :
Hv=tran(Hv)=inv(Hv)
Par convention la matrice unité est une matrice de Householder.
On écrit le programme houdeholder qui à un vecteur v renvoie
la matrice de Householder associé à v.
householder(v):={ local n,w,nv; n:=size(v); nv:=v*v; w:=tran(v); return normal(idn(n)-2*w*[v]/nv); };
Soit Hv la matrice de Householder associée à v.
Etant donné un vecteur a non nul, il existe deux vecteurs v tels
que le vecteur
b=Hv*tran(a) a ses n−1 dernières composantes nulles (Hv étant
la matrice de Householder associé à v).
Plus précisement, si e0=[1,0,..0],e1=[0,1,0,..0] etc...,
les vecteurs v sont :
v1=a+norm(a)*e0 et v2=a-norm(a)*e0.
Plus généralement, il existe deux vecteurs v
tels que le vecteur
b=Hv*tran(a) a ses composantes à partir de la
(l+1)-ième nulles.
Plus précisement, si el=[0,..0,1,0,..0] (ou toutes les composantes sont
nulles sauf el[l]=1), et si
al=[0,...,0,a[l],..,a[n-1]], les vecteurs v sont :
v1=al+norm(al)*el et v2=al-norm(al)*el.
On 'ecrit le programme qui étant donné a et l renvoie si
a est nul renvoie la matrice identité et si a est non nul renvoie
la matrice de Householder associée à v=al+eps*norm(al)*el
en choisisant eps égal au signe de al si al est réelle et
eps=1 sinon.
//renvoie une matrice hh= H tel que b:=H*tran(a) //a ses n-l-1 dernieres comp=0 //ou encore b[l+1]=...b[n-1]=0 make_house(a,l):={ local n,na,el,v; n:=size(a); for (k:=0;k<l;k++) a[k]:=0; na:=normal(norm(a)^2); na:=sqrt(na); if (na==0) return idn(n); el:=makelist(0,1,n); el[l]:=1; if (im(a[l])==0 and a[l]<0) v:=normal(a-na*el); else v:=normal(a+na*el); return householder(v); };
Le programme tridiaghouse(A) renvoie H,B tel que normal(H*A*tran(H))=B (ou encore normal(tran(H)*B*H)=A) avec H est le produit de matrice de householder (tran(H)=inv(H)) et B est une matrice de hessenberg (ou bien B est une matrice tridiagonale si tran(A)=A).
tridiaghouse(A):={ local n, a, H,Ha; B:=A; n:=size(A); H:=idn(n); for (l:=0;l<n-2;l++) { a:=col(B,l); Ha:=make_house(a,l+1); B:=normal(Ha*B*Ha); H:=normal(Ha*H); } //normal(H*A*tran(H))=B et B de hessenberg //(ou tridiagonale si tran(A)=A) return(H,B); };
On tape :
A:=[[3,2,2,2,2],[2,1,2,-1,-1],[2,2,1,-1,1],
[2,-1,-1,3,1],[2,-1,1,1,2]]
H,B:tridiaghouse(A)
On obtient :
[[1,0,0,0,0],[0,1/-2,1/-2,1/-2,1/-2],[0,5*sqrt(11)/22,
(-(3*sqrt(11)))/22,sqrt(11)/22,(-(3*sqrt(11)))/22],
[0,0,22*sqrt(2)/44,0,(-(22*sqrt(2)))/44],[0,22*sqrt(22)/242,
22*sqrt(22)/484,(-(22*sqrt(22)))/121,22*sqrt(22)/484]],
[[3,-4,0,0,0],[-4,9/4,sqrt(11)/4,0,0],[0,sqrt(11)/4,3/4,
44*sqrt(22)/121,0],[0,0,44*sqrt(22)/121,1/2,286*sqrt(11)/484],
[0,0,0,286*sqrt(11)/484,7/2]]
On vérifie et on tape :
normal(H*A*tran(H))
On tape :
A:=[[1,2,3],[2,3,4],[3,4,5]]
H,B:=tridiaghouse(A)
On obtient :
[[1,0,0],[0,(-(2*sqrt(13)))/13,(-(3*sqrt(13)))/13],
[0,(-(3*sqrt(13)))/13,(2*sqrt(13))/13]],
[[1,-(sqrt(13)),0],[-(sqrt(13)),105/13,8/13],[0,8/13,(-1)/13]]
On vérifie et on tape :
normal(H*A*tran(H))