# CAMA : ma21 Surrelaxation pour Gauss-Seidel -- Exercice
# Cours du 11/05
```python=
import numpy as np
import scipy.linalg as lin
import matplotlib.pylab as plt
%matplotlib inline
np.set_printoptions(precision=3, linewidth=150, suppress=True)
```
On va augmenter le rayon de convergence la méthode Jacobi amméliorée faite en TD à savoir la méthode de Gauss-Seidel.
On étudiera sa convergence dans différents cas.
## Gauss-Seidel
Lorsqu'on calcul le **x** suivant avec Jacobi on ne profite pas du fait que N est triangulaire
et donc qu'on connait la nouvelle valeur de $x_n$ lorsqu'on calcule $x_{n-1}$. Avec Gauss-Seidel
on utilise toujours la dernière valeur calculée ce qui accélère la convergence.
Pour résumer Gauss-Seidel d'un point de vu matriciel on a :
* D = la matrice diagonale extraite de A : `D = np.diag(np.diag(A))`
* L = la matrice stritecement triangulaire inférieure de A : `L = np.tril(A, -1)`
* U = la matrice stritecement triangulaire supérieure de A : `U = np.triu(A, 1)`
et une itération est donnée par la formule suivante :
$$
(D + L){\bf x}^{k+1} = -U{\bf x}^k + {\bf b}
$$
ou
$$
{\bf x}^{k+1} = D^{-1} \, ( -L\, {\bf x}^{k+1} - U\; {\bf x}^k + {\bf b})
$$
c.a.d.
$$
\begin{bmatrix}
x_{1}^{k+1} \\
x_{2}^{k+1} \\
\vdots \\
x_{n}^{k+1} \\
\end{bmatrix}=
\begin{bmatrix}
1/a_{11} \quad 0 \quad \ldots \quad 0 \\
0 \quad 1/a_{22} \quad \ldots \quad 0 \\
\vdots \\
0 \quad 0 \quad \ldots \quad 1/a_{nn} \\
\end{bmatrix}
\;
\left(
\;-
\begin{bmatrix}
0 \quad 0 \quad \ldots \quad 0 \\
a_{21} \; 0 \quad \ldots \quad 0 \\
\vdots \\
a_{n1} \, a_{n2} \; \ldots \quad 0 \\
\end{bmatrix}
\;
\begin{bmatrix}
x_{1}^{k+1} \\
x_{2}^{k+1} \\
\vdots \\
x_{n}^{k+1} \\
\end{bmatrix}-
\begin{bmatrix}
0 \; a_{12} \; \ldots \; a_{1n} \\
0 \quad 0 \; \ldots \; a_{2n} \\
\vdots \\
0 \quad 0 \; \ldots \; 0 \\
\end{bmatrix}
\;
\begin{bmatrix}
x_{1}^k \\
x_{2}^k \\
\vdots \\
x_{n}^k \\
\end{bmatrix}
+
\begin{bmatrix}
b_{1} \\
b_{2} \\
\vdots \\
b_{n} \\
\end{bmatrix}
\; \right)
$$
Notons que je peux mettre $L\, {\bf x}^{k+1}$ à droite du signe égal si je résoud mon système ligne par ligne en commencant par le haut puisque dans ce cas les ${\bf x}^{k+1}$ utilisés sont connus. C'est ce qu'on a fait lors du dernier TP.
## Surrelaxation de Gauss-Seidel
Comme on a fait avec Jacobi, on introduit de l'inertie avec $w$ :
$$
{\bf x}^{k+1} = w \, D^{-1} \, ( -L\, {\bf x}^{k+1} - U\; {\bf x}^k + {\bf b}) + (1-w) \; {\bf x}^k
$$
Vérifiez que l'on arrive à l'écriture matricielle suivante :
$$
\left(\frac{D}{w} + L\right)\, {\bf x}^{k+1} = \left(\frac{1-w}{w} \, D - U\right)\; {\bf x}^k + {\bf b}
$$
Écrit ainsi on voit que cette méthode consiste à avoir les éléments de la diagonale des 2 cotés de l'égalité. On peut interpréter cela comme un avantage lié à un gain d'information lors des opérations matrice vecteur.
## Programmons Gauss-Seidel surrelaxé
On écrira deux fonctions :
* `solve_triangular(L,b)` qui retourne la solution de L **x** = **b** lorsque L est triangulaire inférieure
* `gauss_seidel_r(A, b, x0, w, n)` qui fait `n` iteration de Gauss-Seidel avec `w` donné en argument à partir
de `x0`.
Cette fonction retourne un tableau des valeurs de **x** calculées (donc tableau en 2D).
Comme toujours, attention à limiter les `for` et à faire le plus possible d'opérations vectorielles et matricielles.
:::spoiler Solution
```python=
def solve_triangular(L, b):
x = np.empty(len(b))
x[0] = b[0] / L[0,0]
for i in range(1,len(L)):
x[i] = (b[i] - L[i,:i] @ x[:i]) / L[i,i]
return x
# je teste
A = np.tril(np.random.randint(10, size=(4,4)))
b = A.sum(axis=1)
solve_triangular(A,b)
```
```
array([1., 1., 1., 1.])
```
```python=
def gauss_seidel_r(A, b, x0, w=0.5, n=100):
D = np.diag(np.diag(A))
L = np.tril(A, -1)
U = np.triu(A, 1)
L = D / w + L
U = ((1-w) / w) * D - U
x = x0
values = []
for _ in range(n):
x = solve_triangular(L, U @ x + b)
values.append(x)
return np.array(values)
```
:::warning
[L'algorithme](https://en.wikipedia.org/wiki/Successive_over-relaxation) de Wikipedia est difficile alire, lent et il s'agit de Jacobi avec relaxation et non Gauss-Seidel avec relaxation, il est tout ce qu'il ne faut pas faire.
:::
```python=
np.random.seed(123)
A = np.random.randint(10, size=(4,4))
b = A.sum(axis=1)
x0 = np.random.random(4)
res = gauss_seidel_r(A, b, x0, w=0.2, n=100)
print(res[-1])
```
```
[1. 1. 1. 1.]
```
```python=
def plot_convergences(values, result):
error = np.square(values - result).sum(axis = -1) / np.square(result).sum(axis=-1)
error2 = np.square(np.diff(values)).sum(axis = -1) / np.square(values).sum(axis=-1)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14,4))
ax1.plot(range(len(error)), error)
ax1.set_title('Erreur absolue normalisée')
ax1.semilogy();
ax2.plot(range(len(error2)), error2)
ax2.set_title('Erreur relative normalisée')
ax2.semilogy()
print("Itération du minimum :",np.argmin(error), np.argmin(error2))
```
```python=
plot_convergences(res, np.ones(4))
```
:::spoiler Resultat
```
Itération du minimum : 99 99
```
![](https://i.imgur.com/2Z2kW3Y.png)
:::
Est-ce que la méthode de Gauss-Seidel non relaxée converge dans ce cas ?
:::spoiler Reponse
```python=
gauss_seidel_r(A, b, x0, w=1, n=100)[-1] # oui
```
```
array([1., 1., 1., 1.])
```
:::
## Le bon cas
Trouver un `seed` qui permet de générer un cas qui ne converge pas avec Gauss-Seidel de base mais qui
converge avec la relaxation ($w=0.2$).
:::spoiler Solution
```python=
seed = 0
while True:
np.random.seed(seed)
A = np.random.randint(10, size=(4,4))
b = A.sum(axis=1)
x0 = np.random.random(4)
res = gauss_seidel_r(A, b, x0, w=0.2, n=100)
res2 = gauss_seidel_r(A, b, x0, w=1, n=100)
if np.square(res[-1] - np.ones(4)).sum() < 0.01 and np.square(res2[-1] - np.ones(4)).sum() > 1 :
print(seed)
break
seed += 1
```
```
87
```
:::
Tracer les courbes de convergence pour le cas retenu avec et sans relaxation.
:::spoiler Solution
```python=
np.random.seed(87)
A = np.random.randint(10, size=(4,4))
b = A.sum(axis=1)
x0 = np.random.random(4)
```
```python=
plot_convergences(gauss_seidel_r(A, b, x0, w=0.2, n=100), np.ones(4))
```
```
Itération du minimum : 95 97
```
![](https://i.imgur.com/3nw6gVl.png)
```python=
plot_convergences(gauss_seidel_r(A, b, x0, w=1, n=100), np.ones(4))
```
```
Itération du minimum : 0 0
```
![](https://i.imgur.com/aS64SeA.png)
:::
## Étude de $w$
Toujours dans notre cas retenu,
indiquer quel est l'intervale de
valeurs de $w$ qui garantit la convergence pour notre système matriciel A **x** = **b** avec toujours le même `x0`
et un nombre d'itérations à déterminer.
Trouver la valeur optimiale de $w$ pour converger le plus rapidement pour ce cas.
La précision demandée pour l'intervale et la valeur optimale est de $10^{-2}$.
:::spoiler Solution
```python=
# utilisons une méthode par dichotomie
wmin = 0
wmax = 1
while wmax - wmin > 1E-2:
w = (wmax + wmin) / 2
res = gauss_seidel_r(A,b,x0, w, n=1000)[-1]
if np.square(res - np.ones(4)).sum(axis=-1) < 1E-3: # converge
wmin = w
else:
wmax = w
print(f"Intervale de convergence : ]0,{(wmin+wmax)/2}]")
```
```
Intervale de convergence : ]0,0.69140625]
```
```python=
plot_convergences(gauss_seidel_r(A, b, x0, w=0.7, n=500), np.ones(4)) # ca converge tres doucement
```
```
Itération du minimum : 493 494
```
![](https://i.imgur.com/JZbebDP.png)
```python=
plot_convergences(gauss_seidel_r(A, b, x0, w=0.7 + 1E-2, n=500), np.ones(4)) # ca diverge clairement
```
![](https://i.imgur.com/77QbOtG.png)
Il semble que $\omega=0.7$ soit la limite de la convergence.
```python=
# Méthode brutale (Newton serait plus joli mais je ne sais pas si vous connaissez)
# de toute facon c'est très rapide
N = 500
best_w = 0
best_it = N
for i in np.arange(0.01, 0.7, 0.01):
it_cv = np.argmax(np.square(gauss_seidel_r(A, b, x0, i, 500) - np.ones(4)).sum(axis=-1) < 1E-6)
# attention, si la réponse est 0 cela veut dire que cela n'est pas descendu en dessous de 1E-6
if it_cv > 0 and it_cv < best_it:
best_w = i
best_it = it_cv
best_w, best_it
```
```
(0.36000000000000004, 154)
```
```python=
plot_convergences(gauss_seidel_r(A, b, x0, w=0.36, n=100), np.ones(4))
```
```
Itération du minimum : 99 99
```
![](https://i.imgur.com/LzqNE9B.png)
:::success
Trouver la valeur optimale de $\omega$ doit bien sûr pouvoir être fait rapidement. Pour certains problèmes particulier on connait la formule qui donne le $\omega$ optimal, sinon il faut utiliser des heuristiques sans garanties.
:::