Adloun

Résolution de systèmes linéaires

Cours · Python (école d'ingénieur), chapitre 12 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun

12.1 Introduction et Motivation

De nombreux problèmes en physique, en ingénierie, et en apprentissage automatique se ramènent à la résolution d'un système d'équations linéaires de la forme , où est une matrice inversible et est un vecteur colonne donné. Ce chapitre présente l'implémentation de la méthode du pivot de Gauss à l'aide de la bibliothèque NumPy, ainsi que des techniques plus avancées comme la décomposition LU et l'analyse du conditionnement. Nous y aborderons la manipulation avancée des tableaux bidimensionnels (coupes, copies, fancy indexing), les opérations élémentaires sur les lignes d'une matrice, et l'importance cruciale du choix du pivot partiel pour assurer la stabilité numérique de l'algorithme face aux erreurs d'arrondis machine.

12.2 Calcul Matriciel avec NumPy

Pour modéliser des vecteurs et des matrices, NumPy fournit le type ndarray. Contrairement aux listes Python, les tableaux NumPy ont une taille fixe et sont homogènes (tous les éléments ont le même type, généralement float).

12.2.1 Création et Types de Données


import numpy as np

# Matrice à partir d'une liste de listes
A = np.array([[3.0, 6.0, 2.0], [2.0, -3.0, 5.0]], dtype=float)

# Matrices particulières
I = np.eye(3)        # Matrice identité 3x3
Z = np.zeros((4, 4)) # Matrice nulle 4x4
O = np.ones((2, 3))  # Matrice de 1 2x3

12.2.2 Coupes et Vues vs Copies

Une coupe (slicing) d'un tableau NumPy ne crée pas un nouvel objet en mémoire mais une vue (un pointeur vers la même zone mémoire). Pour dupliquer réellement les données, il faut utiliser la méthode copy() :


A = np.array([[1, 2], [3, 4]])

# Vue : modification affecte A
ligne1 = A[0]
ligne1[0] = 99
print(A)  # [[99, 2], [3, 4]]

# Copie : modification n'affecte pas A
ligne2 = A[1].copy()
ligne2[0] = 88
print(A)  # [[99, 2], [3, 4]] (inchangé)

12.2.3 Fancy Indexing (Indexation Avancée)

NumPy permet d'échanger des lignes ou des colonnes de manière concise :


# Permutation des lignes i et j
A[[i, j]] = A[[j, i]]

# Permutation des colonnes i et j
A[:, [i, j]] = A[:, [j, i]]

12.2.4 Produit Matriciel

Attention, l'opérateur * effectue un produit élément par élément (produit de Hadamard). Le produit matriciel mathématique ou s'effectue à l'aide de la fonction np.dot() ou de l'opérateur @ :


A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

C = A @ B  # Produit matriciel
# [[1*5+2*7, 1*6+2*8], [3*5+4*7, 3*6+4*8]] = [[19, 22], [43, 50]]

12.3 Méthode du Pivot de Gauss

Pour résoudre , nous appliquons des opérations élémentaires sur les lignes du système augmenté afin de rendre la matrice triangulaire supérieure (descente), puis diagonale (remontée), avant d'isoler les inconnues.

12.3.1 L'Importance de la Stabilité : Choix du Pivot Partiel

Le choix du pivot lors de l'élimination est crucial. Diviser par un pivot trop petit en valeur absolue amplifie dramatiquement les erreurs d'arrondis.

Exemple 12.1Désastre numérique avec un petit pivot

Considérons le système à 4 chiffres significatifs en base 10 :

La solution exacte est , .

  • Sans permutation (pivot très petit) : Le calcul donne la constante multiplicatrice . L'opération conduit aux approximations d'arrondis. Lors de la remontée, on trouve et (soit 167% d'erreur sur !).
  • Avec permutation (pivot partiel) : On cherche le coefficient maximal en valeur absolue sur la colonne 1, qui est . On permute . La constante devient . La remontée donne et .
Définition 12.2Stratégie du Pivot Partiel

Avant de traiter la colonne de la matrice, on recherche la ligne pour laquelle est maximal. On permute alors les lignes et de et .

12.3.2 Algorithme et Implémentation en Python


def recherche_pivot(A, b, j):
    """
    Recherche le pivot partiel sur la colonne j.
    Permute les lignes j et i_max dans A et b.
    """
    n = A.shape[0]
    i_max = j
    for i in range(j + 1, n):
        if abs(A[i, j]) > abs(A[i_max, j]):
            i_max = i
    if i_max != j:
        A[[j, i_max]] = A[[i_max, j]]
        b[[j, i_max]] = b[[i_max, j]]

def descente(A, b):
    """
    Étape de descente : transforme A en matrice triangulaire supérieure.
    """
    n = A.shape[0]
    for j in range(n - 1):
        recherche_pivot(A, b, j)
        pivot = A[j, j]
        if abs(pivot) < 1e-15:
            raise ValueError("Matrice singulière ou presque singulière.")
        for i in range(j + 1, n):
            facteur = A[i, j] / pivot
            A[i] = A[i] - facteur * A[j]
            b[i] = b[i] - facteur * b[j]

def remontee(A, b):
    """
    Étape de remontée : résout le système triangulaire supérieur.
    """
    n = A.shape[0]
    x = np.zeros(n)
    for i in range(n - 1, -1, -1):
        somme = 0.0
        for j in range(i + 1, n):
            somme += A[i, j] * x[j]
        x[i] = (b[i] - somme) / A[i, i]
    return x

def solve_gauss(A, b):
    """
    Résout le système Ax = b par la méthode de Gauss avec pivot partiel.
    """
    Ac = A.astype(float).copy()
    bc = b.astype(float).copy()
    descente(Ac, bc)
    return remontee(Ac, bc)

12.3.3 Complexité Algorithmique

Proposition 12.3Complexité de la méthode de Gauss

Pour un système de équations à inconnues :

  • La recherche du pivot sur la colonne effectue comparaisons.
  • L'élimination lors de la descente requiert opérations.
  • La remontée requiert opérations.

La complexité temporelle globale est donc , et la complexité spatiale .

12.4 Décomposition LU

La décomposition (où est triangulaire inférieure avec des 1 sur la diagonale et triangulaire supérieure) est intimement liée à l'élimination de Gauss. Les facteurs d'élimination sont stockés dans et la matrice triangulée dans .

12.4.1 Principe et Implémentation


def lu_decomposition(A):
    """
    Décompose A = LU sans pivotage.
    Retourne L et U.
    """
    n = A.shape[0]
    L = np.eye(n)
    U = A.astype(float).copy()

    for j in range(n - 1):
        for i in range(j + 1, n):
            factor = U[i, j] / U[j, j]
            L[i, j] = factor
            U[i, j:] -= factor * U[j, j:]

    return L, U

# Résolution de Ax = b via LU : LUx = b
def solve_lu(A, b):
    L, U = lu_decomposition(A)

    # Ly = b (descente)
    n = len(b)
    y = np.zeros(n)
    for i in range(n):
        y[i] = b[i] - np.dot(L[i, :i], y[:i])

    # Ux = y (remontée)
    x = np.zeros(n)
    for i in range(n - 1, -1, -1):
        x[i] = (y[i] - np.dot(U[i, i+1:], x[i+1:])) / U[i, i]
    return x

12.4.2 Avantages de LU

Une fois la décomposition calculée (coût en ), on peut résoudre efficacement des systèmes avec plusieurs seconds membres en opérations chacun.

12.5 Déterminant et Inverse

12.5.1 Déterminant via la décomposition LU

Le déterminant d'une matrice triangulaire est le produit de ses éléments diagonaux. Pour (avec pivotage partiel), le déterminant est :

Comme et où est le nombre de permutations de lignes effectuées, on a :


def determinant(A):
    """Calcule le déterminant par la méthode de Gauss avec pivot partiel."""
    A_copy = A.astype(float).copy()
    n = A_copy.shape[0]
    det_sign = 1

    for j in range(n - 1):
        # Recherche du pivot
        i_max = j
        for i in range(j + 1, n):
            if abs(A_copy[i, j]) > abs(A_copy[i_max, j]):
                i_max = i
        if i_max != j:
            A_copy[[j, i_max]] = A_copy[[i_max, j]]
            det_sign *= -1

        if abs(A_copy[j, j]) < 1e-15:
            return 0.0

        for i in range(j + 1, n):
            factor = A_copy[i, j] / A_copy[j, j]
            A_copy[i, j:] -= factor * A_copy[j, j:]

    det = det_sign * np.prod([A_copy[i, i] for i in range(n)])
    return det

12.6 Conditionnement d'une Matrice

Le conditionnement d'une matrice mesure sa sensibilité aux erreurs sur les données.

Définition 12.4Conditionnement

Le conditionnement d'une matrice (pour la norme 2) est :

où et sont les valeurs singulières extrêmes de .

Un système est bien conditionné si est proche de 1, et mal conditionné si est grand.

Exemple 12.5Matrice de Hilbert

La matrice de Hilbert d'éléments est célèbre pour être extrêmement mal conditionnée même pour de petites valeurs de . Par exemple, pour :


def hilbert(n):
    """Génère la matrice de Hilbert d'ordre n."""
    H = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            H[i, j] = 1.0 / (i + j + 1)
    return H

# Analyse du conditionnement
for n in [2, 3, 4, 5, 6, 7, 8]:
    H = hilbert(n)
    cond = np.linalg.cond(H)
    print(f"n = {n}, kappa(H) = {cond:.2e}")

12.7 Utilisation de SciPy

Pour le calcul scientifique professionnel, on utilise les fonctions du module scipy.linalg :


from scipy.linalg import solve, lu, det, inv, cond

# Résolution directe
x = solve(A, b)

# Décomposition LU
P, L, U = lu(A)  # PA = LU

# Déterminant
d = det(A)

# Inverse
A_inv = inv(A)

# Conditionnement
c = cond(A)

12.8 Exercices résolus

Niveau 1 : Manipulation de matrices avec NumPy

Exercice 1 - Création et opérations de base

Créer les matrices suivantes et effectuer les opérations demandées :

  • et .
  • Calculer , , (produit matriciel).
  • Calculer la transposée de et la trace de .
Démonstration (Solution)

import numpy as np

# Création des matrices
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

print("A =")
print(A)
print("\nB =")
print(B)

# Opérations élément par élément
print("\nA + B =")
print(A + B)

print("\nA - B =")
print(A - B)

# Produit matriciel
print("\nA @ B (produit matriciel) =")
print(A @ B)

# Transposée
print("\nTransposée de A =")
print(A.T)

# Trace (somme des éléments diagonaux)
print(f"\nTrace de A = {np.trace(A)}")

Résultat dans la console :


A =
[[1 2]
 [3 4]]

B =
[[5 6]
 [7 8]]

A + B =
[[ 6  8]
 [10 12]]

A - B =
[[-4 -4]
 [-4 -4]]

A @ B (produit matriciel) =
[[19 22]
 [43 50]]

Transposée de A =
[[1 3]
 [2 4]]

Trace de A = 5
Exercice 2 - Vues vs copies

Expliquer et illustrer la différence entre une vue et une copie dans NumPy.

La suite du cours est sur Adloun

Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.

Lire la suite du cours

En accès libre, sans inscription.

Exercices corrigés de ce chapitre

Lire la suite du cours →