Raffinement itératif

Objectifs d'apprentissage

À la fin de cette leçon, vous serez en mesure de :

  • Comprendre le principe du raffinement itératif
  • Implémenter l'algorithme de raffinement
  • Analyser quand et pourquoi cette technique fonctionne
  • Identifier les situations où le raffinement est efficace
  • Calculer le gain de précision attendu

Prérequis

  • Conditionnement des systèmes linéaires
  • Factorisation LU

Motivation : récupérer la précision perdue

Dans la leçon précédente, nous avons vu qu'un système mal conditionné peut amplifier les erreurs de manière catastrophique. Avec un conditionnement κ10k\kappa \approx 10^k, on perd environ kk chiffres significatifs.

Question : Peut-on faire mieux sans changer de méthode de résolution ?

Réponse : Oui ! Le raffinement itératif permet de récupérer une partie (voire la totalité) de la précision perdue, en réutilisant la factorisation LU déjà calculée.

💡

Idée clé

Même si la solution xx^* a une erreur significative, on peut calculer le résidu r=bAxr = b - Ax^* avec plus de précision. Ce résidu nous donne une information sur l'erreur, qu'on peut exploiter pour corriger la solution.


Principe de l'algorithme

L'idée fondamentale

Soit xx^* notre solution approchée du système Ax=bAx = b. Définissons :

  • L'erreur : e=xxe = x - x^* (la vraie solution moins notre approximation)
  • Le résidu : r=bAxr = b - Ax^* (ce qui « manque » pour satisfaire l'équation)

Ces deux quantités sont liées par une relation simple :

Ae=A(xx)=AxAx=bAx=rA \cdot e = A(x - x^*) = Ax - Ax^* = b - Ax^* = r

Observation cruciale

L'erreur ee satisfait le même système que xx, mais avec rr comme second membre !

Ae=rA \cdot e = r

Pourquoi c'est utile ?

  1. On ne peut pas calculer ee directement (on ne connaît pas xx)
  2. Mais on peut calculer r=bAxr = b - Ax^* (on connaît AA, bb et xx^*)
  3. En résolvant Ae=rAe = r, on obtient une approximation de l'erreur
  4. On corrige : x=x+ex^{**} = x^* + e

L'algorithme complet

🚨

Algorithme de raffinement itératif

Entrée : Matrice AA, vecteur bb, solution initiale x(0)x^{(0)}, factorisation PA=LUPA = LU

Pour k=0,1,2,k = 0, 1, 2, \ldots jusqu'à convergence :

  1. Calculer le résidu : r(k)=bAx(k)r^{(k)} = b - A \cdot x^{(k)}
  2. Résoudre le système : Ae(k)=r(k)A \cdot e^{(k)} = r^{(k)} (via LULU)
  3. Corriger la solution : x(k+1)=x(k)+e(k)x^{(k+1)} = x^{(k)} + e^{(k)}
  4. Si r(k)/b<ε\|r^{(k)}\| / \|b\| < \varepsilon, arrêter

Sortie : Solution raffinée x(k+1)x^{(k+1)}

Visualisation interactive

La visualisation ci-dessous illustre le raffinement itératif sur le système mal conditionné de la leçon précédente. Cliquez sur les boutons pour voir l'évolution pas à pas avec tous les détails des calculs.

Le système utilisé

C'est le même système mal conditionné que dans la leçon sur le conditionnement :

A=(1111,01),b=(22,01),x=(11)A = \begin{pmatrix} 1 & 1 \\ 1 & 1{,}01 \end{pmatrix}, \quad b = \begin{pmatrix} 2 \\ 2{,}01 \end{pmatrix}, \quad x^* = \begin{pmatrix} 1 \\ 1 \end{pmatrix}

Avec κ(A)400\kappa(A) \approx 400, on perd environ 2-3 chiffres significatifs lors de la résolution par LU.

Comment lire cette visualisation

En haut — Le graphique :

  • Montre l'évolution du nombre de chiffres corrects
  • Chaque point correspond à une itération

En bas — Les détails de l'itération :

Pour chaque itération, on affiche les trois quantités clés :

QuantitéNotationSignification
Solution courantex(k)x^{(k)}Notre approximation actuelle de la solution
Résidur=bAx(k)r = b - Ax^{(k)}Ce qui « manque » pour satisfaire l'équation
Correctionee (solution de Ae=rAe = r)Ce qu'on ajoute pour améliorer x(k)x^{(k)}

Ce qu'on observe

  • Initial : La solution LU a environ 2 chiffres corrects (erreur 102\sim 10^{-2})
  • Iter 1 : On récupère 2 chiffres → 4 chiffres corrects
  • Iter 2 : On gagne encore → 8 chiffres corrects
  • Iter 3 : On atteint la précision machine (≈ 15 chiffres)
💡

Observation clé

Remarquez que l'erreur diminue exponentiellement à chaque itération. Le résidu devient de plus en plus petit, et la correction aussi. C'est la signature du raffinement : tant que κεm<1\kappa \cdot \varepsilon_m < 1, chaque itération divise l'erreur.


Exemple détaillé pas à pas

Prenons un système 2×2 mal conditionné pour illustrer le raffinement.

Le système

A=(1111,005),b=(22,005)A = \begin{pmatrix} 1 & 1 \\ 1 & 1{,}005 \end{pmatrix}, \quad b = \begin{pmatrix} 2 \\ 2{,}005 \end{pmatrix}

Solution exacte : x=(1,1)Tx = (1, 1)^T

Conditionnement : κ(A)800\kappa(A) \approx 800 (on perd ~3 chiffres)

Étape 0 : Solution initiale (avec erreurs d'arrondi simulées)

Supposons qu'à cause des erreurs d'arrondi, notre solveur LU donne :

x(0)=(0,998,1,003)Tx^{(0)} = (0{,}998, 1{,}003)^T

au lieu de (1,1)T(1, 1)^T. L'erreur initiale est :

evraie(0)=xx(0)=(0,002,0,003)Te^{(0)}_{\text{vraie}} = x - x^{(0)} = (0{,}002, -0{,}003)^T

Itération 1

Étape 1 : Calculer le résidu

r(0)=bAx(0)r^{(0)} = b - A \cdot x^{(0)}

Calculons Ax(0)A \cdot x^{(0)} :

Ax(0)=(1111,005)(0,9981,003)=(0,998+1,0030,998+1,008015)=(2,0012,006015)A \cdot x^{(0)} = \begin{pmatrix} 1 & 1 \\ 1 & 1{,}005 \end{pmatrix} \begin{pmatrix} 0{,}998 \\ 1{,}003 \end{pmatrix} = \begin{pmatrix} 0{,}998 + 1{,}003 \\ 0{,}998 + 1{,}008015 \end{pmatrix} = \begin{pmatrix} 2{,}001 \\ 2{,}006015 \end{pmatrix}

Donc :

r(0)=(22,005)(2,0012,006015)=(0,0010,001015)r^{(0)} = \begin{pmatrix} 2 \\ 2{,}005 \end{pmatrix} - \begin{pmatrix} 2{,}001 \\ 2{,}006015 \end{pmatrix} = \begin{pmatrix} -0{,}001 \\ -0{,}001015 \end{pmatrix}
💡

Observation

Le résidu est petit (103\sim 10^{-3}), mais l'erreur sur xx est plus grande (103\sim 10^{-3} aussi dans ce cas, mais le rapport peut varier selon κ\kappa).

Étape 2 : Résoudre Ae=rA \cdot e = r

On résout :

(1111,005)(e1e2)=(0,0010,001015)\begin{pmatrix} 1 & 1 \\ 1 & 1{,}005 \end{pmatrix} \begin{pmatrix} e_1 \\ e_2 \end{pmatrix} = \begin{pmatrix} -0{,}001 \\ -0{,}001015 \end{pmatrix}

Par élimination (ou en réutilisant LU) :

  • Ligne 2 - Ligne 1 : 0,005e2=0,001015(0,001)=0,0000150{,}005 \cdot e_2 = -0{,}001015 - (-0{,}001) = -0{,}000015
  • Donc e2=0,003e_2 = -0{,}003
  • De la ligne 1 : e1=0,001e2=0,001(0,003)=0,002e_1 = -0{,}001 - e_2 = -0{,}001 - (-0{,}003) = 0{,}002

On obtient :

e(0)=(0,002,0,003)Te^{(0)} = (0{,}002, -0{,}003)^T

Remarque

On a retrouvé exactement l'erreur vraie ! En pratique, ce ne sera qu'une approximation, mais souvent très bonne.

Étape 3 : Corriger la solution

x(1)=x(0)+e(0)=(0,9981,003)+(0,0020,003)=(11)x^{(1)} = x^{(0)} + e^{(0)} = \begin{pmatrix} 0{,}998 \\ 1{,}003 \end{pmatrix} + \begin{pmatrix} 0{,}002 \\ -0{,}003 \end{pmatrix} = \begin{pmatrix} 1 \\ 1 \end{pmatrix}

Résultat : Après une seule itération, on a récupéré la solution exacte !


Pourquoi le raffinement fonctionne-t-il ?

Le secret : la précision du calcul du résidu

Le résidu r=bAxr = b - Ax^* peut être calculé avec plus de précision que la solution elle-même. Voici pourquoi :

Calcul de xx^* : Nécessite de résoudre un système linéaire, ce qui accumule des erreurs d'arrondi amplifiées par κ(A)\kappa(A).

Calcul de rr : C'est un simple produit matrice-vecteur suivi d'une soustraction. Les erreurs d'arrondi ne sont pas amplifiées par κ\kappa.

💡

Précision du résidu

En arithmétique flottante standard, le résidu calculé r^\hat{r} satisfait :

r^=r+O(εmAx)\hat{r} = r + O(\varepsilon_m \|A\| \|x^*\|)

εm\varepsilon_m est l'epsilon machine. L'erreur est de l'ordre de εm\varepsilon_m, indépendamment de κ(A)\kappa(A) !

Analyse de convergence

Soit e(k)=xx(k)e^{(k)} = x - x^{(k)} l'erreur à l'itération kk. On peut montrer que :

e(k+1)κ(A)εme(k)\|e^{(k+1)}\| \lesssim \kappa(A) \cdot \varepsilon_m \cdot \|e^{(k)}\|

Interprétation :

  • Si κ(A)εm<1\kappa(A) \cdot \varepsilon_m < 1, l'erreur diminue à chaque itération
  • Le facteur de réduction est environ κ(A)εm\kappa(A) \cdot \varepsilon_m

Exemple : Avec κ=108\kappa = 10^8 et εm=1016\varepsilon_m = 10^{-16} (double précision) :

κεm=108×1016=108\kappa \cdot \varepsilon_m = 10^8 \times 10^{-16} = 10^{-8}

Chaque itération réduit l'erreur d'un facteur 10810^8 ! En 2 itérations, on peut gagner 16 chiffres.

Limite de la méthode

⚠️

Condition de convergence

Le raffinement itératif converge si et seulement si :

κ(A)εm<1\kappa(A) \cdot \varepsilon_m < 1

Avec εm1016\varepsilon_m \approx 10^{-16}, cela nécessite κ(A)<1016\kappa(A) < 10^{16}.

Si κ(A)>1016\kappa(A) > 10^{16}, le raffinement ne converge pas — la matrice est trop mal conditionnée pour être traitée en double précision.


Amélioration : calcul du résidu en précision étendue

Pour maximiser l'efficacité du raffinement, on peut calculer le résidu en précision étendue :

Technique du « double-double »

L'idée est de calculer r=bAxr = b - Ax^* en accumulant les produits avec plus de précision :

residual_extended.pypython
import numpy as np

def residual_extended(A, b, x):
  """
  Calcule le résidu r = b - A·x avec précision étendue.
  Utilise l'algorithme de sommation compensée de Kahan.
  """
  n = len(b)
  r = np.zeros(n)

  for i in range(n):
      # Initialiser avec b[i]
      s = b[i]
      c = 0.0  # Compensation

      # Soustraire A[i,:] · x avec compensation
      for j in range(n):
          # Produit exact en deux parties
          prod = A[i, j] * x[j]

          # Sommation compensée (Kahan)
          y = -prod - c
          t = s + y
          c = (t - s) - y
          s = t

      r[i] = s

  return r
💡

Gain de précision

Avec le calcul compensé du résidu, on peut souvent récupérer la précision machine complète même pour des systèmes modérément mal conditionnés.


Implémentation complète

raffinement_iteratif.pypython
import numpy as np
from scipy.linalg import lu_factor, lu_solve

def raffinement_iteratif(A, b, max_iter=10, tol=1e-14, verbose=True):
  """
  Résout Ax = b avec raffinement itératif.

  Paramètres:
  -----------
  A : ndarray (n, n)
      Matrice du système
  b : ndarray (n,)
      Second membre
  max_iter : int
      Nombre maximum d'itérations de raffinement
  tol : float
      Tolérance sur le résidu relatif
  verbose : bool
      Afficher les informations de convergence

  Retourne:
  ---------
  x : ndarray (n,)
      Solution raffinée
  info : dict
      Informations sur la convergence
  """
  n = len(b)

  # Étape 1 : Factorisation LU (une seule fois !)
  lu, piv = lu_factor(A)

  # Étape 2 : Solution initiale
  x = lu_solve((lu, piv), b)

  # Stocker l'historique
  residuals = []

  # Étape 3 : Raffinement itératif
  for k in range(max_iter):
      # Calculer le résidu r = b - A·x
      r = b - A @ x

      # Norme relative du résidu
      res_rel = np.linalg.norm(r) / np.linalg.norm(b)
      residuals.append(res_rel)

      if verbose:
          print(f"Itération {k}: ||r||/||b|| = {res_rel:.2e}")

      # Test de convergence
      if res_rel < tol:
          if verbose:
              print(f"Convergence atteinte en {k+1} itérations")
          break

      # Résoudre A·e = r (réutilise la factorisation LU)
      e = lu_solve((lu, piv), r)

      # Corriger la solution
      x = x + e

  info = {
      'iterations': k + 1,
      'residuals': residuals,
      'final_residual': res_rel
  }

  return x, info


# ============================================================
# EXEMPLE D'UTILISATION
# ============================================================

if __name__ == "__main__":
  # Créer une matrice mal conditionnée
  n = 5

  # Matrice de Hilbert (très mal conditionnée)
  H = np.array([[1.0/(i+j+1) for j in range(n)] for i in range(n)])

  # Solution exacte connue
  x_exact = np.ones(n)

  # Second membre
  b = H @ x_exact

  # Calculer le conditionnement
  kappa = np.linalg.cond(H)
  print(f"Conditionnement κ(H) = {kappa:.2e}")
  print(f"Chiffres perdus attendus : {np.log10(kappa):.1f}")
  print()

  # Résoudre avec raffinement
  x, info = raffinement_iteratif(H, b, verbose=True)

  # Erreur finale
  erreur = np.linalg.norm(x - x_exact) / np.linalg.norm(x_exact)
  print(f"\nErreur relative finale : {erreur:.2e}")

Quand utiliser le raffinement itératif ?

Situations favorables

SituationEfficacitéCommentaire
κ<106\kappa < 10^6Excellente1-2 itérations suffisent généralement
106<κ<101210^6 < \kappa < 10^{12}Bonne2-5 itérations, récupère plusieurs chiffres
1012<κ<101610^{12} < \kappa < 10^{16}ModéréeConverge lentement, gain limité
κ>1016\kappa > 10^{16}InefficaceNe converge pas en double précision

Avantages

  • Coût faible : Chaque itération coûte O(n2)O(n^2) (produit matrice-vecteur + résolution triangulaire)
  • Réutilise LU : La factorisation (coûteuse, O(n3)O(n^3)) n'est faite qu'une fois
  • Simple à implémenter : Quelques lignes de code supplémentaires
  • Amélioration garantie : Si κεm<1\kappa \cdot \varepsilon_m < 1, l'erreur diminue

Inconvénients

  • Limité par le conditionnement : Ne fonctionne pas si κ\kappa est trop grand
  • Stockage de AA : On doit garder la matrice originale (pas seulement LU)
  • Pas de miracle : Ne transforme pas un problème mal posé en problème bien posé

Comparaison avec et sans raffinement

Exemple numérique

Considérons la matrice de Hilbert H6H_6 avec κ107\kappa \approx 10^7 :

MéthodeErreur relativeChiffres corrects
LU seul109\sim 10^{-9}≈ 9
LU + 1 raffinement1014\sim 10^{-14}≈ 14
LU + 2 raffinements1015\sim 10^{-15}≈ 15

On récupère environ 6 chiffres (ce qui correspond à log10(κ)7\log_{10}(\kappa) \approx 7) !


Résumé

Le raffinement itératif est une technique simple et efficace pour améliorer la précision d'une solution obtenue par factorisation LU.

Algorithme :

  1. Calculer le résidu r=bAxr = b - Ax^*
  2. Résoudre Ae=rAe = r en réutilisant LU
  3. Corriger xx+ex^* \leftarrow x^* + e
  4. Répéter si nécessaire

Points clés :

  • Converge si κ(A)εm<1\kappa(A) \cdot \varepsilon_m < 1
  • Chaque itération coûte O(n2)O(n^2) seulement
  • Peut récupérer jusqu'à log10(κ)\log_{10}(\kappa) chiffres perdus
  • Le calcul du résidu en précision étendue améliore encore l'efficacité

Pour aller plus loin

La prochaine leçon présentera les méthodes itératives (Jacobi, Gauss-Seidel, SOR), une approche alternative aux méthodes directes pour les grands systèmes creux.