Série 5 : Méthodes itératives pour la résolution de systèmes linéaires

1. Théorique

Exercice 1

On considère le système linéaire \(A\mathbf{x}=\mathbf{b}\), où

\[A = \begin{pmatrix} 6 & -3 & 0 \\ -3 & 6 & 4\\ 0 & 4 & 6 \end{pmatrix}, \quad \mathbf{b}=\begin{pmatrix}1\\ 2\\ -3\end{pmatrix}.\]
  1. Démontrer que, s’il existe une constante \(0<C<1\) telle que, pour tout \(k\in \mathbb{N}\),

    \[\|\mathbf{x}^{(k+1)}-\mathbf{x}\|_{A} \leq C\| \mathbf{x}^{(k)}-\mathbf{x}\|_{A} \, ,\]

    alors on a la majoration de l’erreur suivante (remarquez que l’estimation est indépendante de la solution \(\mathbf{x}\)):

    \[\|\mathbf{x}^{(k)}-\mathbf{x}\|_{A} \leq \frac{C^k}{1-C}\| \mathbf{x}^{(1)}-\mathbf{x}^{(0)}\|_{A}.\]

    Sugg.: estimer \(\|\mathbf{x}^{(0)}-\mathbf{x}\|_{A}\) par rapport à \(\|\mathbf{x}^{(0)}-\mathbf{x}^{(1)}\|_{A}\).

  2. On considère la méthode de Richardson stationnaire préconditionné, avec matrice de préconditionnement \(P=D\), \(D\) étant la partie diagonale de \(A\). La méthode est-elle convergente? Calculer le paramètre \(\alpha_{opt}\) optimal (qui caractérise la méthode du gradient stationnaire).

  3. On considère maintenant la méthode du gradient préconditionné(Richardson dynamique), toujours avec préconditionneur \(P=D\). La méthode est-elle convergente? Calculer le facteur \(C\) de réduction de l’erreur, tel que

    \[\|\mathbf{x}^{(k+1)}-\mathbf{x}\|_{A} \leq C\| \mathbf{x}^{(k)}-\mathbf{x}\|_{A}.\]
  4. En choisissant comme vecteur initial \(\mathbf{x}^{(0)}=(0,0,0)^T\), estimer le nombre minimal d’itérations nécessaires pour avoir une erreur (en norme \(\|\cdot \|_{A}\)) plus petite que \(10^{-8}\).

Solution
  1. Les valeurs propres de \(P^{-1}A\) son strictement positives,

    \[\sigma(P^{-1}A)=\left\{ 1\pm\frac{5}{6},1 \right\}.\]

    Par la théorie on a

    \[\alpha_{opt}=\frac{2}{\lambda_{min}+\lambda_{max}},\]

    donc \(\alpha_{opt}=1\).

  2. Par la théorie on sait que

    \[C=\frac{K_2(P^{-1}A)-1}{K_2(P^{-1}A)+1},\]

    et comme \(P^{-1}A\) est symétrique et définie positive on a \(K_2(P^{-1}A)=\lambda_{max}/ \lambda_{min}\). Alors \(C=5/6\).

  3. On a

    \[\|\mathbf{x}^{(k)} - \mathbf{x}\|_{A} \leq C \|\mathbf{x}^{(k-1)} - \mathbf{x}\|_{A} \leq C^{k} \|\mathbf{x}^{(0)} - \mathbf{x}\|_{A}.\]

    D’ailleurs on a aussi

    \[\begin{gathered} \|\mathbf{x}^{(0)} - \mathbf{x}\|_{A} \leq \|\mathbf{x}^{(0)} - \mathbf{x}^{(1)}\|_{A} + \|\mathbf{x}^{(1)} - \mathbf{x}\|_{A} \leq \|\mathbf{x}^{(0)} - \mathbf{x}^{(1)}\|_{A} + C \|\mathbf{x}^{(0)}-\mathbf{x}\|_{A} \\ \Longrightarrow \|\mathbf{x}^{(0)} - \mathbf{x}\|_{A} \leq \frac{1}{1-C}\|\mathbf{x}^{(1)} - \mathbf{x}^{(0)}\|_{A} \end{gathered}\]

    et on a bien le résultat cherché.

  4. On utilise la méthode stationnaire pour calculer \(\mathbf{x}^{(1)}\). On a \(\mathbf{x}^{(0)} = (0, 0, 0)^T\) et \(\mathbf{r}^{(0)} = \mathbf{b}\). Donc (avec \(\alpha_{opt}=1\))

    \[\begin{aligned} & \mathbf{x}^{(1)} = P^{-1}\mathbf{r}^{(0)} = \left(\frac{1}{6}, \frac{1}{3}, -\frac{1}{2} \right)^{T} \quad \text{et} \\ & \|\mathbf{x}^{(1)} - \mathbf{x}^{(0)}\|_{A} = \|\mathbf{x}^{(1)}\|_{A} =\sqrt{(\mathbf{x}^{(1)}, A \mathbf{x}^{(1)})} = \sqrt{\frac{2}{3}}. \end{aligned}\]

    On impose donc

    \[\|\mathbf{x}^{(k)} - \mathbf{x}\|_{A} \leq 6 \left(\frac{5}{6}\right)^{k} \sqrt{\frac{2}{3}} \leq 10^{-8}\Longrightarrow k \geq \frac{8 + \log_{10} \sqrt{24}}{\log_{10} 6/5} \approx %118.5. 109.7.\]
Exercice 2

On considère les systèmes linéaires \(A \mathbf{x}=\mathbf{b}\), où:

\[A = \left( \begin{array}{ccccc} 2 & -1 & & \textrm{\huge{0}} \\ -1 & 2 & \ddots & \\ & \ddots & \ddots & -1 \\ \textrm{\huge{0}} & & -1 & 2 \end{array} \right) \in\mathbb{R}^{10\times 10} \; , \quad \mathbf{b} = A \ \left( \begin{array}{c} 1\\ 1\\ \vdots \\ 1 \end{array} \right)\in\mathbb{R}^{10} .\]
  • Analyser le rayon spéctral de la matrice d’itération de la méthode SOR en fonction du paramètre de relaxation \(\omega=0,0.1,0.2,\ldots,2\). Donner la valeur \(\omega_{opt}\) pour laquelle la méthode converge le plus vite dans cet exemple.

  • En utilisant le fichier sor, comparer le nombre d’itérations effectuées par la méthode SOR en fonction de \(\omega\) pour achever une erreur en dessous d’une tolérance \(\verb|tol|=10^{-9}\) et \(\mathbf{x}_0 = (0, \ldots, 0)^\top\). Quel est le facteur qu’on peut gagner en nombre d’itérations si on utilise \(\omega=\omega_{opt}\) calculé au point précédent par rapport à Gauss-Seidel (\(\omega=1\)).

Solution

D’abord on définit la matrice \(A\) et on vérifie qu’elle est définie positive. La symétrie est évidente.

import numpy as np
import matplotlib.pyplot as plt

n = 5  # ou tout autre nombre approprié
A = 2 * np.diag(np.ones(n)) - np.diag(np.ones(n - 1), -1) - np.diag(np.ones(n - 1), 1)
np.min(np.linalg.eigvals(A))

+ Donc la matrice \(A\) est s.d.p. et par conséquence la méthode SOR converge pour \(0<\omega<2\). Les commandes suivantes définissent la partie diagonale, triangulaire inférieure et supérieure de \(A\) ainsi que quelques paramètres pour effectuer la méthode SOR et le membre de droite \(\mathbf{b}\):

D = np.diag(np.diag(A))
E = -np.tril(A, -1)
F = -np.triu(A, 1)
NMAX = 10000
TOL = 1e-9
X0 = np.zeros(n)
b = A @ np.ones(n)

+ La boucle "for" suivante calcule pour chaque \(\omega=0, 0.1, \ldots, 2\) le rayon spectral de la matrice d’itération ainsi que la solution de la méthode, et puis sauvegarde le nombre d’itérations nécessaires:

# Note: La fonction sor() doit être définie ou importée
count = 0
omega_range = np.arange(0, 2.1, 0.1)
rho = []
iter = []
for omega in omega_range:
    count += 1
    B = np.linalg.inv(np.eye(n) - omega * np.linalg.inv(D) @ E) @ ((1 - omega) * np.eye(n) + omega * np.linalg.inv(D) @ F)
    rho.append(max(abs(np.linalg.eigvals(B))))
    # X, ITER = sor(A, b, X0, NMAX, TOL, omega)
    # iter.append(ITER)

+ Finalement on affiche le résultat:

plt.figure(1)
plt.plot(omega_range, rho)
plt.figure(2)
plt.plot(omega_range, iter)
plt.show()

+ Les graphiques sont illustrés dans la Fig. 1.

Finalement on calcule \(\omega_{opt}\) et le nombre d’itérations correspondant, ainsi que pour \(\omega=1\).

m, argm = min((val, idx) for (idx, val) in enumerate(rho))
omega_opt = omega_range[argm]
iter_at_omega_opt = iter[argm]

arg1 = np.where(omega_range == 1)[0][0]
iter_at_omega_1 = iter[arg1]

+ Donc on gagne un facteur 5.3721 en nombre d’itérations par rapport à la méthode de Gauss-Seidel (\(\omega=1\))!

Exercice 3

Considérons la méthode du gradient (dite aussi méthode de Richardson dynamique sans préconditionnement).

  1. Montrer que le paramètre d’accélération \(\alpha_k\) correspond à la solution du problème de minimisation suivant :

    \[\alpha_k=\mbox{arg min}\, \Phi(\mathbf{x}^{(k)}+\alpha\mathbf{r}^{(k)}),\]

    où \(\Phi\) désigne l’énergie du système \(A\mathbf{x}=\mathbf{b}\), définie par \(\Phi(\mathbf{x})=\tfrac12\mathbf{x}^TA\mathbf{x}-\mathbf{x}^T\mathbf{b}\). Dans ce cas on dit que \(\mathbf{x}^{(k)}\) est optimal par rapport à la direction \(\mathbf{r}^{(k)}\).

  2. Montrer que \((\mathbf{r}^{(k+1)},\mathbf{r}^{(k)})=0\).

  3. Interpréter géométriquement la méthode du gradient.

Solution
  1. Le minimum de la fonction \(\alpha\in \mathbb{R}\longrightarrow\Phi(\mathbf{x}^{(k)}+\alpha\mathbf{r}^{(k)})\), est atteint dans le point \(\alpha^*\) où la dérivée s’annule, autrement dit,

    \[\begin{aligned} 0&=\left.\frac{d}{d\alpha}\Phi(\mathbf{x}^{(k)}+\alpha\mathbf{r}^{(k)})\right|_{\alpha=\alpha^*}\\ &=(\nabla\Phi(\mathbf{x}^{(k)}+\alpha^*\mathbf{r}^{(k)}),\mathbf{r}^{(k)} )\\ &=(A(\mathbf{x}^{(k)}+\alpha^*\mathbf{r}^{(k)})-\mathbf{b},\mathbf{r}^{(k)} )\\ &=(\alpha^*A\mathbf{r}^{(k)}-\mathbf{r}^{(k)},\mathbf{r}^{(k)})\\ &=\alpha^*(A\mathbf{r}^{(k)},\mathbf{r}^{(k)})-(\mathbf{r}^{(k)},\mathbf{r}^{(k)}), \end{aligned}\]

    ce qui donne \(\alpha^*=\alpha_k\).

  2. D’après le point précédent on a

    \[0=(\nabla\Phi(\mathbf{x}^{(k)}+\alpha_k\mathbf{r}^{(k)}),\mathbf{r}^{(k)} )= (\nabla\Phi(\mathbf{x}^{(k+1)}),\mathbf{r}^{(k)} )=-(\mathbf{r}^{(k+1)}, \mathbf{r}^{(k)}).\]
  3. Le problème est de déterminer le minimiseur \({\mathbf{x}}\) de \(\Phi\) en partant d’un point \({\mathbf{x}}^{(0)} \in \mathbb{R}^n\), ce qui revient à déterminer des directions de déplacement qui permettent de se rapprocher le plus possible de la solution x. L’idée la plus naturelle est de prendre la direction de descente de pente maximale \(\mathbf{r}^{(k)}=-\nabla \Phi({\mathbf{x}}^{(k)})\). Puis, chaque itérant \({\mathbf{x}}^{(k)}\) minimise \(\Phi\) le long de cette direction de descente. Ainsi, les directions sont consécutivement orthogonales.

Exercice 4

Il s’agit de résoudre le système linéaire \(A\mathbf{x}=\mathbf{b}\), où \(A\) et \(\mathbf{b}\) sont

\[A=\begin{bmatrix} 2 & -1\\ -1 & 2 \end{bmatrix},\quad \mathbf{b}=\begin{bmatrix} 1\\ 1 \end{bmatrix} .\]
  • Vérifier que \(\operatorname{A}\) est symétrique définie positive.

  • Effectuer deux itérations de la méthode du gradient conjugué en partant de \(\displaystyle \mathbf{x}^{(0)}=\begin{bmatrix} 3/2\\ 2 \end{bmatrix}.\) Vérifier que \(\mathbf{x}^{(2)}=\mathbf{x}\), où x est la solution du système linéaire \(A\mathbf{x}=\mathbf{b}\).

  • Soit \(\displaystyle \Phi(\mathbf{x})=\frac{1}{2}(A\mathbf{x},\mathbf{x})-(\mathbf{b},\mathbf{x})\) la fonctionnelle d’énergie associée au système linéaire. Représenter graphiquement sur le plan \(\mathbb{R}^2\) les points \(\mathbf{x}^{(0)},\mathbf{x}^{(1)}\) et \(\mathbf{x}^{(2)}\), ainsi que les lignes de niveau de \(\Phi\) passant par ces points.

Solution
  • Nous observons que \(A\) est symétrique. On sait que \(A\) est définit positive si et seulement si tout ces sous-matrices principales sont de déterminant positif. On a que \(|A_1|=2>0\) et que \(|A|=|A_2|=3>0\). En conséquence la matrice \(A\) est symétrique et définie positive.

  • Pour \(k=0\), on obtient

    \[\alpha_0 = \frac12,\quad \mathbf{x}^{(1)}=\begin{bmatrix} 3/2\\ 5/4 \end{bmatrix}, \quad \mathbf{r}^{(1)}=\begin{bmatrix} -3/4\\ 0 \end{bmatrix}, \quad \beta_0 = -\frac14,\quad \mathbf{p}^{(1)}=\begin{bmatrix} -3/4\\ -3/8 \end{bmatrix}.\]

    Pour l’itération suivant on obtient

    \[\alpha_1 = \frac23,\quad \mathbf{x}^{(2)}=\begin{bmatrix} 1\\ 1 \end{bmatrix}.\]

    On peut vérifier que \(\mathbf{x}^{(2)}\) est la solution du système.

  • La solution est dessinée dans la figure suivante.

    image

voici le code qui permet de l’obtenir

import numpy as np

# Définir A et b
A = np.array([[2, -1], [-1, 2]])
b = np.array([1, 1])

# Vérifier si A est symétrique définie positive
if np.allclose(A, A.T) and np.all(np.linalg.eigvals(A) > 0):
    print("A est symétrique définie positive.")
else:
    print("A n'est pas symétrique définie positive.")

# Méthode du gradient conjugué
def conjugate_gradient(A, b, x0, tol=1e-10, max_iter=1000):
    x = x0
    r = b - np.dot(A, x)
    d = r.copy()
    rs_old = np.dot(r.T, r)
    x_list = [x.copy()]

    for i in range(max_iter):
        Ad = np.dot(A, d)
        alpha = rs_old / np.dot(d.T, Ad)
        x += alpha * d
        r -= alpha * Ad
        rs_new = np.dot(r.T, r)
        if np.sqrt(rs_new) < tol:
            break
        d = r + (rs_new / rs_old) * d
        rs_old = rs_new
        x_list.append(x.copy())

    return x, x_list

# Appliquer la méthode
x0 = np.array([1.5, 2])
x_sol, x_list = conjugate_gradient(A, b, x0, max_iter=2)
print( f"x_list={x_list}, x_sol={x_sol}" )

# Fonctionnelle d'énergie Phi
def phi(x):
    return 0.5 * np.dot(x, A @ x) - np.dot(b, x)

# Tracer les lignes de niveau de Phi
x_vals = np.linspace(0, 2, 400)
y_vals = np.linspace(0, 2, 400)
X, Y = np.meshgrid(x_vals, y_vals)
Z = np.array([[phi(np.array([x, y])) for x, y in zip(x_row, y_row)] for x_row, y_row in zip(X, Y)])

phi_levels = sorted([phi(x) for x in x_list])

import plotly.graph_objs as go


# Créer la figure avec les lignes de niveau
fig = go.Figure(data =
    go.Contour(
        z=Z,
        x=x_vals,
        y=y_vals,
        contours=dict(
            start=min(phi_levels),
            end=max(phi_levels),
            size=int(max(phi_levels)/len(phi_levels))
        ),
        colorscale='Blues'
    )
)

# Ajouter les points et les annotations
for i, point in enumerate(x_list):
    fig.add_trace(go.Scatter(x=[point[0]], y=[point[1]], mode='markers+text',
                             text=[f'x^{i}'],
                             textposition="bottom center",
                             marker=dict(color='red', size=10)))

# Ajouter x_sol
fig.add_trace(go.Scatter(x=[x_sol[0]], y=[x_sol[1]], mode='markers',
                         marker=dict(color='green', size=10)))

# Tracer les segments entre les points
for i in range(len(x_list) - 1):
    fig.add_trace(go.Scatter(x=[x_list[i][0], x_list[i+1][0]],
                             y=[x_list[i][1], x_list[i+1][1]],
                             mode='lines',
                             line=dict(color='red')))
fig.add_trace(go.Scatter(x=[x_list[-1][0], x_sol[0]],
                         y=[x_list[-1][1], x_sol[1]],
                         mode='lines',
                         line=dict(color='red')))

# Mise en forme finale de la figure
fig.update_layout(title='Lignes de niveau de Phi et points de la méthode du gradient conjugué',
                  xaxis_title='x1',
                  yaxis_title='x2')
fig.show()
Results
A est symétrique définie positive.
x_list=[array([1.5, 2. ]), array([1.5 , 1.25])], x_sol=[1. 1.]



    
Exercice 5

Considérons la méthode du gradient conjugué sans préconditionnement.

  • Démontrer que le résidu \(\mathbf{r}^{(k+1)}\) est orthogonal aux diréctions \(\mathbf{p}^{(j)}\), \(j=0,\ldots,k\), c’est-à-dire

    \[(\mathbf{p}^{(j)},\mathbf{r}^{(k+1)} )= 0 \quad\mbox{pour}\quad j =0,\ldots,k,\]

    que les résidue sont tous orthogonales, c’est-à-dire

    \[(\mathbf{r}^{(j)},\mathbf{r}^{(k+1)} )= 0 \quad\mbox{pour}\quad j =0,\ldots,k,\]

    et que les directions successives sont \(A\)-orthogonales, c’est-à-dire

    \[(\mathbf{p}^{(j)},\mathbf{p}^{(k+1)})_A =0 \quad\mbox{pour}\quad j=0,\ldots,k.\]

    Pour faire cela procéder comme suit:

    • Démontrer d’abord l’hypothèse initiale

      \[(\mathbf{p}^{(0)},\mathbf{r}^{(1)})=0, \quad (\mathbf{r}^{(0)},\mathbf{r}^{(1)})=0 \quad\mbox{et} \quad (\mathbf{p}^{(0)},\mathbf{p}^{(1)})_A=0.\]
    • Supposer l’hypothèse de récurrence suivante

      \[\begin{aligned} &&(\mathbf{p}^{(j)},\mathbf{r}^{(k)})=0 \quad\mbox{pour}\quad j=0,\ldots,k-1,\\ &&(\mathbf{r}^{(j)},\mathbf{r}^{(k)})=0 \quad\mbox{pour}\quad j=0,\ldots,k-1,\\ &&(\mathbf{p}^{(j)},\mathbf{p}^{(k)})_A=0 \quad\mbox{pour}\quad j=0,\ldots,k-1, \end{aligned}\]

      et démontrer que

      \[\begin{aligned} &&(\mathbf{p}^{(j)},\mathbf{r}^{(k+1)} )= 0 \quad\mbox{pour}\quad j =0,\ldots,k,\\ &&(\mathbf{r}^{(j)},\mathbf{r}^{(k+1)} )= 0 \quad\mbox{pour}\quad j =0,\ldots,k,\\ &&(\mathbf{p}^{(j)},\mathbf{p}^{(k+1)})_A=0 \quad\mbox{pour}\quad j=0,\ldots,k. \end{aligned}\]
    • Conclure par récurrence.

Solution
    • On démontre d’abord l’hypothèse initiale. On a que \(\mathbf{p}^{(0)}=\mathbf{r}^{(0)}\) et \(\mathbf{r}^{(1)}=\mathbf{r}^{(0)}-\alpha_0A\mathbf{p}^{(0)}\) où \(\alpha_0\) est donné par

      \[\alpha_0 = \frac{\|\mathbf{r}^{(0)}\|_2^2}{(\mathbf{r}^{(0)},A\mathbf{r}^{(0)})}.\]

      Donc on conclut que \((\mathbf{p}^{(0)},\mathbf{r}^{(1)}) = (\mathbf{r}^{(0)},\mathbf{r}^{(1)}) =0\). De plus \((\mathbf{p}^{(0)},\mathbf{p}^{(1)})_A= (A\mathbf{p}^{(0)},\mathbf{r}^{(1)}-\beta_0\mathbf{p}^{(0)})\) avec

      \[\beta_0 = \frac{(A\mathbf{p}^{(0)},\mathbf{r}^{(1)})}{(A\mathbf{p}^{(0)},\mathbf{p}^{(0)})}\]

      et donc \((\mathbf{p}^{(0)},\mathbf{p}^{(1)})_A=0\).

    • Noter que

      \[(\mathbf{p}^{(j)},\mathbf{r}^{(k+1)} ) = (\mathbf{p}^{(j)},\mathbf{r}^{(k)} ) -\alpha_k (A\mathbf{p}^{(j)},\mathbf{p}^{(k)} )\]

      et noter que pour \(j=0,\ldots,k-1\) les deux produit scalaires du membre de droite s’annule par l’hypothèse de récurrence. Pour \(j=k\) noter que par le choix de

      \[\alpha_k = \frac{(\mathbf{p}^{(k)},\mathbf{r}^{(k)})}{(\mathbf{p}^{(k)},A\mathbf{p}^{(k)})}\]

      et donc par construction, le terme \((\mathbf{p}^{(k)},\mathbf{r}^{(k+1)} )\) s’annule également. En conséquence

      \[ (\mathbf{p}^{(j)},\mathbf{r}^{(k+1)} ) = 0 \quad \mbox{pour}\quad j=0,\ldots, k.\]

      Deuxièmement noter que

      \[\begin{aligned} (\mathbf{r}^{(j)},\mathbf{r}^{(k+1)} ) &=& (\mathbf{p}^{(j)},\mathbf{r}^{(k+1)} ) +\beta_{j-1}(\mathbf{p}^{(j-1)},\mathbf{r}^{(k+1)} ) = 0\quad \mbox{pour}\quad j=1,\ldots, k,\\ (\mathbf{r}^{(0)},\mathbf{r}^{(k+1)} ) &=& (\mathbf{p}^{(0)},\mathbf{r}^{(k+1)} )= 0 \end{aligned}\]

      par [eq1] et donc

      \[ (\mathbf{r}^{(j)},\mathbf{r}^{(k+1)} ) = 0 \quad \mbox{pour}\quad j=0,\ldots, k.\]

      Troisièment observer que

      \[ (\mathbf{p}^{(j)},\mathbf{p}^{(k+1)} )_A = (\mathbf{p}^{(j)},\mathbf{r}^{(k+1)} )_A - \beta_k (\mathbf{p}^{(j)},\mathbf{p}^{(k)} )_A.\]

      Supposons d’abord que \(j<k\). Donc \((\mathbf{p}^{(j)},\mathbf{p}^{(k)} )_A=0\) par hypothèse de récurrence et

      \[(\mathbf{p}^{(j)},\mathbf{p}^{(k+1)} )_A = (\mathbf{p}^{(j)},\mathbf{r}^{(k+1)} )_A = (A\mathbf{p}^{(j)},\mathbf{r}^{(k+1)} ) \quad \mbox{pour}\quad j=0,\ldots, k-1.\]

      Remarquer que \(A\mathbf{p}^{(j)}= \frac{1}{\alpha_j} (\mathbf{r}^{(j)}-\mathbf{r}^{(j+1)})\), en conséquence \(A\mathbf{p}^{(j)} \in \mbox{span}\{\mathbf{r}^{(0)},\ldots,\mathbf{r}^{(k)}\}\), et donc

      \[(\mathbf{p}^{(j)},\mathbf{p}^{(k+1)} )_A = \tfrac{1}{\alpha_j} (\mathbf{r}^{(j)},\mathbf{r}^{(k+1)} ) - \tfrac{1}{\alpha_j} (\mathbf{r}^{(j+1)},\mathbf{r}^{(k+1)} ) \quad \mbox{pour}\quad j=0,\ldots, k-1.\]

      Par [eq3] on conclut que

      \[(\mathbf{p}^{(j)},\mathbf{p}^{(k+1)} )_A = 0 \quad \mbox{pour}\quad j=0,\ldots, k-1.\]

      Supposons ensuite que \(j=k\). En introduisant dans [eq2] le choix

      \[\beta_k = \frac{(\mathbf{p}^{(k)},\mathbf{r}^{(k+1)})_A}{(\mathbf{p}^{(k)},\mathbf{p}^{(k)})_A}\]

      nous amène directement que \((\mathbf{p}^{(k)},\mathbf{p}^{(k+1)} )_A=0\) par construction et donc

      \[(\mathbf{p}^{(j)},\mathbf{p}^{(k+1)} )_A = 0 \quad \mbox{pour}\quad j=0,\ldots, k.\]
    • On conclut par récurrence.

2. Python

Exercice 6

On veut résoudre le système linéaire

\[A \mathbf{x} = \mathbf{b}\]

où \(A\) est la matrice de Hilbert de taille \(5\) (commande hilb(5)), et \(\mathbf{b}\) est un vecteur tel que la solution du système soit \(\mathbf{x} = (1,1,1,1,1)^t\) (c’est-à-dire ones(5,1)). Vérifier s’il est possible d’appliquer la méthode du gradient et du gradient conjugué pour résoudre ce système.

  • Essayer de résoudre le système avec la méthode du gradient non préconditionné (Richardson dynamique). Utiliser tan.syslin.gradient avec le préconditionneur \(I_5\) (la matrice identité de taille 5, np.eye(5)), une tolérance de \(10^{-15}\), un vecteur initial \(\mathbf{x}^{(0)}=(0,0,0,0,0)^t\) et un nombre maximum d’itérations égal à \(10^5\). Est-ce que la méthode converge?

  • Utiliser maintenant le préconditionneur \(P\) défini par la partie triangulaire inférieure de \(A\) (commande np.tril(A)). Est-ce que la méthode du gradient préconditionné est convergente? Combien d’itérations sont nécessaires?

  • Résoudre le même système avec la méthode du gradient conjugué ( tan.syslin.pcg) non-préconditionné. Combien d’itérations sont nécessaires? Comparez avec les points a) et b).

Solution
  • On définit d’abord la matrice A et le vecteur b:

    import numpy as np
    from scipy.linalg import hilbert
    
    A = hilbert(5)
    b = A @ np.ones(5)

    La matrice A est symétrique définie positive (pour vérifier, l’on peut montrer avec la commande np.linalg.eigvals(A) que les valeurs propres de A sont strictement positives). On peut donc appliquer les méthodes du gradient et du gradient conjugué.

    La méthode du gradient n’arrive pas à approcher la solution avec une tolérance de \(10^{-15}\) en \(10^5\) itérations, comme on le voit avec la commande suivante:

    from tan.syslin import gradient
    P = np.eye(5)
    x, iter = gradient(A, b, P, x0=np.zeros(5), tol=1e-15, maxiter=1e5)
    # gradient reached the maximum iteration number without converging.
    #
    # x =
    #     1.0000
    #     1.0004
    #     0.9984
    #     1.0024
    #     0.9988
    # iter =
    #     100000
    pour utiliser la méthode du gradient non-préconditionné on a choisi le préconditionneur égal à la matrice identité (\(P=I\)).
  • De la même façon, on voit que la méthode du gradient préconditionné avec np.tril(A) converge avec \(92517 < 10^5\) itérations:

    P = np.tril(A)
    x, iter = gradient(A, b, P=P, x0=np.zeros(5), tol=1e-15, maxiter= 1e5)
    # gradient converged in 92517 iterations.
    #
    # x =
    #     1.0000
    #     1.0000
    #     1.0000
    #     1.0000
    #     1.0000
    # iter =
    #    92517
    La proprieté de convergence de la méthode du gradient préconditionné (Richardson dynamique) que l’on a vu au cours a été énoncée dans le cas où \(P\) est symétrique définie positive: ce qui n’est pas le cas avec \(P=\)np.tril(A). Néanmoins, on observe une réduction d’itérations nécessaires pour satisfaire le critère de convergence en dessous de 100'000 itérations.
  • On utilise maintenant la méthode du gradient conjugué:

    from scipy.sparse.linalg import cg
    
    x, info = cg(A, b, tol=1e-15, maxiter=10000)
    # pcg converged at iteration 8 to a solution with relative residual 2.3e-16
    #
    # x =
    #     1.0000
    #     1.0000
    #     1.0000
    #     1.0000
    #     1.0000

    Cette méthode converge en seulement \(8\) itérations. Il faut remarquer qu’en précision infinie, la méthode du gradient conjugué converge en au plus \(n\) itérations, où \(n\) est la taille de la matrice du système. Dans cet exercice, \(8>n=5\), à cause des erreurs d’arrondi.

Exercice 7

On considère les systèmes linéaires \(A_\epsilon\mathbf{x}=\mathbf{b}_\epsilon\), où:

\[A_\epsilon = \left( \begin{array}{cccccc} 1 & \epsilon & \epsilon^2 & & \textrm{\huge{0}} \\ \epsilon & 1 & \epsilon & \ddots & \\ \epsilon^2 & \epsilon & \ddots & \ddots & \epsilon^2 \\ & \ddots & \ddots & 1 & \epsilon \\ \textrm{\huge{0}} & & \epsilon^2 & \epsilon & 1 \end{array} \right) \; , \quad \mathbf{b}_\epsilon = A_\epsilon \ \left( \begin{array}{c} 1\\ 1\\ 1 \\ 1 \\ \vdots \\ 1 \end{array} \right), \quad \epsilon \in [0,1] \; .\]

Pour une taille n et une valeur epsi de \(\epsilon\) données, il est possible d’utiliser la commande

from tan.matrix import matrix
A, b = matrix(5, 0.1)
print(f"A =\n {A}")
print(f"b =\n {b}")
Results
A =
 [[1.   0.1  0.01 0.   0.  ]
 [0.1  1.   0.1  0.01 0.  ]
 [0.01 0.1  1.   0.1  0.01]
 [0.   0.01 0.1  1.   0.1 ]
 [0.   0.   0.01 0.1  1.  ]]
b =
 [1.11 1.21 1.22 1.21 1.11]

pour construire la matrice \(A_\epsilon\) et le vecteur \(\mathbf{b}_\epsilon\) du système. Dans la suite on considère n=5.

  • On considère la matrice A = \(A_\epsilon\) avec \(\epsilon=0.6\) et une taille relativement grande, notamment n=1000.

    • Quel est le rapport entre le nombre des éléments non-nuls de A et le nombre des éléments nuls (voir commande nnz)?

    • Le format de stockage sparse (fr. creux) permet de mémoriser seulement les éléments non-nuls d’une matrice. Serait-il utile de mémoriser A avec un tel format? Vérifier votre réponse en comparant l’occupation mémoire (utiliser whos sous ipython après avoir défini la matrice aux formats creux et dense) de A et de sa copie de type sparse, As = csr_matrix(A).

      import numpy as np
      from scipy.sparse import csr_matrix
      
      # Créer une matrice dense avec de nombreux zéros
      dense_matrix = np.array([
          [1, 0, 0, 0, 2],
          [0, 0, 3, 0, 0],
          [4, 0, 0, 0, 5],
          [0, 6, 0, 0, 0],
          [7, 0, 0, 8, 9]
      ])
      
      # Convertir la matrice dense en une matrice Compressed Sparse Row (CSR)
      sparse_matrix = csr_matrix(dense_matrix)
      
      print(sparse_matrix)
      Results
        (0, 0)	1
        (0, 4)	2
        (1, 2)	3
        (2, 0)	4
        (2, 4)	5
        (3, 1)	6
        (4, 0)	7
        (4, 3)	8
        (4, 4)	9

      vous pouvez donc utiliser la commande csr_matrix pour convertir une matrice dense en une matrice creuse et inversement la commande todense() pour convertir une matrice creuse en une matrice dense.

      # Convertir la matrice éparse en une matrice dense
      dense_matrix = sparse_matrix.todense()
      print(dense_matrix)
      Results
      [[1 0 0 0 2]
       [0 0 3 0 0]
       [4 0 0 0 5]
       [0 6 0 0 0]
       [7 0 0 8 9]]
    • Calculer le rapport entre les temps de calcul de la solution \(\mathbf{x}\) avec les deux formats de stockage, lorsque on utilise la méthode de Richardson stationnaire (commande richardson), avec le paramètre \(\alpha=0.3\), une tolérance de \(\verb|Tol| = 10^{-9}\), \(\mathbf{x_0}=(0,\ldots,0)^\top\) et sans préconditionnement (c’est-à-dire \(P=I_n\)). Chaque temps peut se calculer avec une ligne du type

      from pytictoc import tic, toc (1)
      t = TicToc() (2)
      t.tic() (3)
      x=richardson(...)
      t.toc() (4)
      1 il faut importer les commandes tic et toc du module pytictoc (commande pip install pytictoc).
      2 on crée un objet t de type TicToc.
      3 on démarre le chronomètre.
      4 on arrête le chronomètre et on affiche le temps écoulé.

      tic et toc sont les commandes “chronomètre” de Python. N’oubliez pas de également mettre la matrice de préconditionnement \(P\) sous forme sparse.

  • On considère finalement la matrice A = \(A_\epsilon\) avec \(\epsilon=0.5\) et n=10. En utilisant la commande eig calculer les valeurs propres de \(A\). Puis tracer le nombre d’itérations de la méthode de Richardson stationnaire en fonction de \(\alpha\in (0,2/{\lambda_{max}})\).

ipython est un shell interactif de Python. Pour lancer ipython il suffit de taper ipython dans un terminal. Pour lancer ipython avec un notebook il suffit de taper ipython notebook dans un terminal. Pour lancer ipython avec un notebook et un support pour le calcul scientifique il suffit de taper ipython notebook --pylab=inline dans un terminal. Pour plus d’informations sur ipython voir http://ipython.org/.
Solution