# CAMA : ma13 Système matriciel -- Exercices # Cours du 04/05 ## Programmation vectorielle :::danger Le but des exercices est * d'avoir un programme qui donne la bonne réponse * qui soit le plus rapide possible (et pour cela on utilise massivement Numpy) ::: :::warning En règle général si vous avez des `for` imbriqués c'est mauvais signe. ::: ## Méthode du pivot de Gauss partiel L'ennoncé est dans le cours :::spoiler Solution ```python= def solve_gauss_partial(A, b): # on prend le max dans la colonne i parmi les lignes en dessous (plus facile) for i in range(len(A)-1): pivot = np.argmax(np.abs(A[i:, i])) # il n'y a que 3 lignes à ajouter pour échanger les lignes A[[i, pivot]] = A[[pivot, i]] b[[i, pivot]] = b[[pivot, i]] E = np.diag(np.array([1.,] * len(A), dtype=A.dtype)) coefs = - A[i+1:,i] / A[i,i] E[i+1:,i] = coefs A[i:, i:] = E[i:,i:] @ A[i:,i:] b[i+1:] += coefs * b[i] # multiplication terme à terme # A est maintenant triangulaire surpérieur res = np.zeros(len(b), dtype=b.dtype) res[-1] = b[-1] / A[-1,-1] for i in range(len(A)-1)[::-1]: res[i] = (b[i] - A[i,i+1:] @ res[i+1:]) / A[i,i] return res ``` ```python= e = 1E-6 A = np.array([[e, 1], [1, 2]], dtype='float32') b = np.array([1., 3.], dtype='float32') print(f"A\n {A} \nb\n {b}\n") x = solve_gauss_partial(A, b) print('solution : ',x) print('vérification\n', A@x) ``` ``` A [[0.000001 1. ] [1. 2. ]] b [1. 3.] solution : [1.0000019 0.99999905] vérification [3. 0.999997] ``` ::: ## Factorisation de Choleski Il s'agit de décomposer A en $A = B\, B^T$ avec B une matrice triangulaire inférieure. Cela n'est possible que si la matrice A est symétrique et définie positive (c'est d'ailleurs un facon de vérifier qu'une matrice est définie positive). Écrire l'algorithme de Choleski qui prend A et retourne B (pour deviner l'algorithme, essayez de trouver les coefficients de B à partir des coefficients de A sur une matrice A 4x4). :::spoiler Solution $$ A = B\, B^T = \begin{bmatrix} b_{11} & 0 & \dots & 0\\ b_{21} & b_{22} & \dots & 0\\ & \vdots&\\ b_{n1} & b_{n2} & \dots& b_{n,n} \end{bmatrix} \begin{bmatrix} b_{11} & b_{21} & \dots & b_{n1}\\ 0 & b_{22} & \dots & b_{n2}\\ & \vdots&\\ b_{n1} & b_{n2} & \dots& b_{n,n} \end{bmatrix}= \begin{bmatrix} b_{11}^2 & b_{11}b_{21} & \dots & b_{11}b_{n1}\\ x & \sum_{i=1}^2b_{2i}^2 & \dots & \sum_{i=1}^2b_{2i}b_{ni}\\ & & \vdots&\\ x & x & \dots& \sum_{i=1}^2b_{n,i}^2 \end{bmatrix} $$ avec $x$ la même valeur que de l'autre coté de la diagonale On voit que $b_{11} = \sqrt{a_{11}}$ et maintenant qu'on a $b_{11}$ on peut trouver toute la première ligne de $B^T$ : $b_{j1}=a_{1j}/b_{11}$. Une fois qu'on connait la première ligne de $B^T$ , on s'attaque à la deuxième en commencant par trouver $b_{22}$ puis ensuite tous les autres éléments de la ligne comme on a fait pour la première ligne. On a donc dans le cas général : * $b_{ii} = \sqrt{a_{ii} - \sum_{k=1}^{i-1}b_{ik}^2}$ * $b_{ji} = a_{ij} - \sum_{k=1}^{i-1}b_{ik}b_{jk}/b_{ii} = a_{ij} - \sum_{k=1}^{i-1}b_{ik}b_{kj}^T/b_{ii} \space\forall j\gt i$ ```python= def Choleski(A): B = np.zeros(A.shape) for i in range(len(A)): B[i,i] = np.sqrt(A[i,i] - np.sum(np.square(B[i, :i]))) # garanti ok car A est def positive B[i+1:, i] = (A[i, i+1:] - B[i, :i] @ B.T[:i, i+1:]) / B[i,i] # les ∑ sous forme de prod. scalaire return B ``` ::: ## Matrice symetrique Rappel : pas de boucles `for` imbriquées mais des opérations vectorielles et matricielles (sur des sous-matrices). Créer une matrice symétrique définie positive est vérifier que votre programme marche bien. :::spoiler Solution ```python= A = np.random.randint(10, size=(4,4)) A = A + A.T # symmétrique A = A + np.diag(A.sum(axis=0)) # diagonale dominante print('A:\n', A) B = Choleski(A) print('B\n', B) print('vérification\n', B @ B.T) ``` ``` A: [[55 8 18 5] [ 8 33 7 10] [18 7 54 9] [ 5 10 9 28]] B [[7.4161984871 0. 0. 0. ] [1.0787197799 5.6423721639 0. 0. ] [2.4271195049 0.7765914857 6.8924593995 0. ] [0.6741998625 1.6434093681 0.8831939788 4.9055711788]] vérification [[55. 8. 18. 5.] [ 8. 33. 7. 10.] [18. 7. 54. 9.] [ 5. 10. 9. 28.]] ``` ::: ## Améliorer Jacobi Lorsqu'on écrit une itération de la méthode de Jacobi avec l'ensemble des coefficients, on constate que si on calcule la nouvelle valeur de **x** élément par élement alors lorsqu'on veut mettre à jour `x[1]`, on connait déjà `x[0]`. Idem lorsqu'on met à jour `x[2]` on connait déjà `x[0]` et `x[1]`, etc. L'idée de la version amméliorée de Jacobi est d'utiliser la nouvelle valeur de `x[0]` et non pas l'ancienne comme c'est le cas dans l'algorithme du cours. Ainsi en utilisant des valeurs mise à jour on peut espérer converger plus vite. Dans ce chaque itération demande un calcul ligne par ligne et donc une boucle `for` dans la boucle `for` sur les itérations. #### Test d'arrêt On ajoutera un argument `error` à la fonction qui indique la précision désirée du résultat. Par défaut sa valeur est de `1E-6` et pour offrir une bonne garantie on arrête l'algorithme lorsque $||x_{t+1} - x_t|| < \texttt{error}\, / \, 10$. :::spoiler Solution ```python= def Jacobi(A, b, error=1E-6, verbose=False): L = np.tril(A) U = -np.triu(A, k=1) if verbose: print(f"L:\n {L}\nU\n {U}\n") previous_x = np.zeros(len(b)) x = np.random.random(len(b)) err = (error / 10) ** 2 while np.sum(np.square(x - previous_x)) > err: previous_x = x.copy() if verbose: print(f"x = {x}") # on résoud L x = U x + b avec L matrice triangulaire inférieure y = U @ x + b x[0] = y[0] / L[0,0] for i in range(1,len(L)): x[i] = (y[i] - L[i,:i] @ x[:i]) / L[i,i] return x ``` ```python= A = np.random.randint(10, size=(4,4)) A = A + np.diag(A.sum(axis=0)) b = A.sum(axis=1) # ainsi la solution est [1,1,1,1] print('A:\n', A, "\nb:\n", b, "\n") Jacobi(A,b, verbose=True) ``` ``` A: [[24 2 1 7] [ 5 19 4 6] [ 9 2 20 9] [ 2 9 9 32]] b: [34 34 40 52] L: [[24 0 0 0] [ 5 19 0 0] [ 9 2 20 0] [ 2 9 9 32]] U [[ 0 -2 -1 -7] [ 0 0 -4 -6] [ 0 0 0 -9] [ 0 0 0 0]] x = [0.8870874823 0.8448958895 0.2146829205 0.8281640711] x = [1.0957657001 1.194392389 1.0147923641 0.9351814319] x = [1.0020897014 1.0168049182 1.0265474982 0.9876765266] x = [1.0010877908 0.9980164155 1.0052544156 0.9990120918] x = [1.0002345046 0.9991440665 1.000424625 1.000106649 ] x = [1.0000225291 0.9998709979 0.9999547701 1.0000475947] x = [0.999998753 0.9999948204 0.9999796615 1.0000072549] x = [0.9999991631 1.000002211 0.9999968908 1.0000003049] x = [0.9999998564 1.0000005961 0.9999998678 0.9999998785] x = [0.9999999913 1.0000000685 1.0000000518 0.9999999667] ``` ``` array([1.0000000018, 0.9999999991, 1.0000000142, 0.9999999961]) ``` :::