La méthode du gradient conjugué
La méthode du gradient consiste essentiellement en deux phases: choisir une direction de descente (celle du résidu) et un point où \(\Phi\) atteint un minimum local le long de cette direction. La seconde phase est indépendante de la première. En effet, étant donné une direction \(\mathbf{p}^{(k)}\), on peut choisir \(\alpha_k\) comme étant la valeur du paramètre \(\alpha\) telle que \(\Phi(\mathbf{x}^{(k)} + \alpha \mathbf{p}^{(k)})\) est minimum. En écrivant que la dérivée par rapport à \(\alpha\) s’annule au point où la fonction admet un minimum local, on obtient
| La question est de savoir comment choisir \(\mathbf{p}^{(k)}\). |
Une alternative à l’approche qui nous a conduit à identifier \(\mathbf{p}^{(k)}\) avec \(\mathbf{r}^{(k)}\) est proposée dans la suite.
Si \(\mathbf{x}^{(k)}\) est optimal par rapport à n’importe quelle direction d’un espace vectoriel \(V\), on dit que \(\mathbf{x}^{(k)}\) est optimal par rapport à \(V\).
Il découle de la définition de l’optimalité que \(\mathbf{p}\) doit être orthogonal au résidu \(\mathbf{r}^{(k)}\). En effet, on déduit de [eq18] que \(\Phi\) admet un minimum local le long de \(\mathbf{p}\) pour \(\lambda=0\). La dérivée de \(\Phi\) par rapport à \(\lambda\) doit donc s’annuler pour \(\lambda=0\). Or
On a donc
c’est-à-dire, \(\mathbf{p} \perp \mathbf{r}^{(k)}\). Remarquer que le vecteur \(\mathbf{x}^{(k+1)}\) de la méthode du gradient (non préconditionné) est optimal par rapport à \(\mathbf{r}^{(k)}\) puisque, d’après le choix de \(\alpha_k\), on a \(\mathbf{r}^{(k+1)} \perp \mathbf{r}^{(k)}\), mais cette propriété n’est plus vraie pour l’itérée suivante \(\mathbf{x}^{(k+2)}\). On est donc amené à se demander s’il existe des directions de descente qui conservent l’optimalité des vecteurs à chaque itération. Soit donc
et supposons \(\mathbf{x}^{(k)}\) optimal par rapport à une direction \(\mathbf{p}\) (ainsi \(\mathbf{r}^{(k)} \perp \mathbf{p}\)). Imposons que \(\mathbf{x}^{(k+1)}\) demeure optimal par rapport à \(\mathbf{p}\), c’est-à-dire \(\mathbf{r}^{(k+1)} \perp \mathbf{p}\). On obtient alors
Ainsi, pour préserver l’optimalité entre les itérées successives, les directions de descentes doivent être mutuellement A-orthogonales (on dit aussi A-conjuguées), i.e.
Une méthode qui utilise des directions de descente A-conjuguées est dite conjuguée. Expliquons maintenant comment générer automatiquement une suite de directions conjuguées. Soit \(\mathbf{p}^{(0)} = \mathbf{r}^{(0)}\) et cherchons une direction de la forme
où \(\beta_k \in \mathbb{R}\) est à déterminer de manière à ce que
On peut montrer (par recurrence) que l’égalité [eq20] est aussi vérifiée pour \(j = 0, 1, \ldots, k-1\).
La méthode du gradient conjugué (notée GC) est donc la méthode obtenue en choisissant comme directions de descente les vecteurs \({\bf p}^{(k)}\) donnés par [eq19] et comme paramètres d’accélération les \(\alpha_k\) définis en [eq17]. Par conséquent, en posant \({\bf r}^{(0)} = {\bf b} - {\rm A} {\bf x}^{(0)}\) et \({\bf p}^{(0)} = {\bf r}^{(0)}\), la \(k\)-ième itération de la méthode du gradient conjugué s’écrit
Concernant la convergence de la méthode du gradient conjugué, on a le résultat suivant:
Preuve
Les directions \({\bf p}^{(0)}, {\bf p}^{(1)}, \ldots, {\bf p}^{(n-1)}\) forment une base A-orthogonale de \(\mathbb{R}^n\). De plus, puisque \({\bf x}^{(n)}\) est optimal par rapport à toutes les directions \({\bf p}^{(j)}\), \(j=0, \ldots, n-1\), le vecteur \({\bf r}^{(n)}\) est orthogonal à l’espace \(S_{n-1} = \langle {\bf p}^{(0)}, {\bf p}^{(1)},\ldots, {\bf p}^{(n-1)} \rangle\). Par conséquent, \({\bf r}^{(n)} \perp S_{n-1} = \mathbb{R}^n\) et donc \({\bf r}^{(n)} = {\bf 0}\) ce qui implique \({\bf x}^{(n)} = {\bf x}\). \(\blacksquare\)
On démontre aussi le théorème suivant:
La \(k\)-ième itération de la méthode du gradient conjugué n’est bien définie que si la direction de descente \({\bf p}^{(k)}\) est non nulle. En outre, si \({\bf p}^{(k)} = {\bf 0}\), alors l’itérée \({\bf x}^{(k)}\) doit coı̈ncider avec la solution x du système.
| En l’absence d’erreur d’arrondi, la méthode du gradient conjugué peut donc être vue comme une méthode directe puisqu’elle converge en un nombre fini d’étapes Néanmoins, pour les matrices de grandes tailles, elle est généralement utilisée comme méthode itérative puisqu’on l’interrompt dès que l’erreur devient inférieure à une tolérance fixée. De ce point de vue, la dépendance de l’erreur par rapport au conditionnement de la matrice est plus favorable que pour la méthode du gradient. Signalons également que l’estimation [eq22] est souvent beaucoup trop pessimiste et ne prend pas en compte le fait que dans cette méthode, et contrairement à la méthode du gradient, la convergence est influencée par la totalité du spectre de A, et pas seulement par les valeurs propres extrémales. |
1. La méthode du gradient conjugué préconditionné (Sec. 4.3.5 du livre)
Si \(P\) est une matrice de préconditionnement symétrique définie positive, la méthode du gradient conjugué préconditionné consiste à appliquer la méthode du gradient conjugué au système préconditionné
En pratique, la méthode est implémentée sans calculer explicitement \(P^{1/2}\) ou \(P^{-1/2}\). Après un peu d’algèbre, on obtient le schéma suivant:
on se donne \({\bf x}^{(0)}\) et on pose \({\bf r}^{(0)} = {\bf b} - {\rm A}{\bf x}^{(0)}\), \({\bf z}^{(0)} = {\rm P}^{-1} {\bf r}^{(0)}\) et \({\bf p}^{(0)} = {\bf z}^{(0)}\), la \(k\)-ième itération s’écrit alors
| Le coût du calcul est plus élevé que pour GC puisqu’on doit résoudre à chaque itération le système linéaire \({\rm P} {\bf z}^{(k+1)} = {\bf r}^{(k+1)}\). L’estimation de l’erreur est la même que pour la méthode non préconditionnée, à condition de remplacer A par \({\rm P}^{-1} {\rm A}\). |
2. Implémentation
Voici une implémentation de la méthode du gradient conjugué préconditionné pour la résolution d’un système linéaire \(A\mathbf{x}=\mathbf{b}\) où \(A\) est une matrice symétrique définie positive et \(P\) est une matrice de préconditionnement symétrique définie positive.
import numpy as np
def pcg(A, b, P=None, x0=None, tol=1e-6, maxiter=100):
"""
Calcule la solution du système linéaire Ax = b en utilisant la méthode du gradient conjugué préconditionné.
Paramètres :
----------
A : ndarray, shape (n, n)
Matrice des coefficients.
b : ndarray, shape (n,)
Vecteur de droite.
P : ndarray, shape (n, n), optionnel
Matrice de préconditionnement. Par défaut, il s'agit de la matrice d'identité.
x0 : ndarray, shape (n,), optionnel
Estimation initiale de la solution. Par défaut, il s'agit d'un vecteur nul.
maxiter : int, optionnel
Nombre maximal d'itérations pour le solveur. La valeur par défaut est 100.
tol : float, optionnel
Tolérance pour le critère de convergence de la norme résiduelle. La valeur par défaut est 1e-6.
Retourne :
-------
x : ndarray, shape (n,)
Solution approximative de Ax = b.
niter : int
Nombre d'itérations effectuées.
inc : liste
L'incrément à chaque itération.
res : list
La norme résiduelle à chaque itération.
Notes :
-----
La méthode du gradient conjugué préconditionné utilise la matrice P pour accélérer la convergence. La méthode itère
tant que la norme résiduelle est supérieure à la tolérance spécifiée ou jusqu'à ce que le nombre maximal d'itérations soit atteint.
"""
if P is None:
P = np.eye(len(A))
if x0 is None:
x0 = np.zeros(len(A))
niter = 0
x = x0
r = b - A @ x
r0 = r
z = np.linalg.solve(P, r)
p = z
res = [1]
inc = []
#while res[-1] > tol and niter < maxiter:
while niter < maxiter:
niter += 1
pold = p
zold = z
rold = r
xold = x
alpha = np.dot(p, rold) / np.dot(p, A @ p)
x = xold + alpha * p
r = rold - alpha * A @ p
z = np.linalg.solve(P, r)
beta = np.dot(z, A @ pold ) / np.dot(pold, A @ pold)
p = z - beta * pold
res.append(np.linalg.norm(r)/np.linalg.norm(r0))
inc.append(np.linalg.norm(x - xold))
if inc[-1] < tol:
break
return x, niter, inc, res
3. Exemple
Reprenons la matrice symétrique définie positive qui nous a servie à illustrer l’algorithme du gradient.
import numpy as np
# pick a 4x4 matrix symétrique définie positive
A=np.array([[2.,-1.,0.,0.],[-1.,2.,-1.,0.],[0.,-1.,2.,-1.],[0.,0.,-1.,2.]])
print(f"A = {A}")
# on calcule les valeurs propres de A
print(f"Valeurs propres de A: {np.linalg.eigvals(A)}")
print(f"Conditionnement de A: {np.linalg.cond(A)}")
Results
A = [[ 2. -1. 0. 0.] [-1. 2. -1. 0.] [ 0. -1. 2. -1.] [ 0. 0. -1. 2.]] Valeurs propres de A: [3.61803399 2.61803399 0.38196601 1.38196601] Conditionnement de A: 9.472135954999578
On peut maintenant résoudre le système \(A\mathbf{x}=\mathbf{b}\) avec la méthode du gradient conjugué préconditionné.
from tan.syslin.pcg import pcg
[x_pcg,niter_pcg,res_pcg,inc_pcg]=pcg( A, np.array([1.,0.,1.,0]), P=np.diag(np.diag(A)) )
print(f"Solution: x = {x_pcg}")
print(f"Nombre d'itérations: {niter_pcg}")
Results
Solution: x = [1.2 1.4 1.6 0.8] Nombre d'itérations: 5
Nous pouvons tracer la convergence du résidu ainsi que celle de l’incrément avec Plotly
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(x=np.arange(0,niter_pcg+1), y=inc_pcg, mode='lines+markers', name='Incrément'))
fig.add_trace(go.Scatter(x=np.arange(0,niter_pcg+1), y=res_pcg, mode='lines+markers', name='Résidu'))
fig.update_layout(title='Convergence de la méthode du gradient conjugué préconditionné', xaxis_title='Itération', yaxis_title='Incrément et Résidu', yaxis_type='log')
fig.show()
Results
| nous observons que le résidu et l’incrément atteignent le 0 machine en 4 itérations. Cela est dû au fait que la matrice \(A\) est de taille 4 et que la méthode du gradient conjugué converge en au plus 4 itérations pour une matrice de taille 4, voir cette remarque. |