Gauss Elimination mit Pivotisierung

Lernziel:

  • Algorithmus der Gauss Elimination schrittweise zu verstehen

  • Kennenlernen der Idee wie das Kopieren der Zeilen vermieden werden kann

Wir betrachten wieder dasselbe lineare Gleichungssystem:

\[\begin{split}\begin{pmatrix} 2 & -1 & -3 & 3\\ 4 & 0 & -3 & 1\\ 6 & 1 & -1 & 6\\ -2& -5 & 4 & 1\end{pmatrix}\cdot x = \begin{pmatrix}1\\ -8\\ -16\\ -12\end{pmatrix}\end{split}\]
[1]:
import numpy as np
[2]:
n = 4
A  = np.array([[2, -1, -3, 3],
               [4,  0, -3, 1],
               [6,  1, -1, 6],
               [-2, -5, 4, 1]],dtype=float)
R = np.array(A) # R-Matrix
L = np.zeros((n,n))   # L-Matrix
A
[2]:
array([[ 2., -1., -3.,  3.],
       [ 4.,  0., -3.,  1.],
       [ 6.,  1., -1.,  6.],
       [-2., -5.,  4.,  1.]])

In der Gauss-Elimination soll das Tauschen der Matrixeinträge vermieden werden. Dazu benutzt man die ind-Liste, in der die Reihenfolge der Zeilen gespeichert wird. Initital ist diese gegeben durch:

[3]:
ind = np.arange(n)
ind
[3]:
array([0, 1, 2, 3])

LR - Faktorisierung

Erste Spalte

Pivotelement ist die grösste Zahl einer Spalte. Wir suchen daher die Zeile mit dem grössten Betrag, wobei als Reihenfolge der Zeilen der Matrix der Vektor ind benutzt wird.

Der Index j geht über die Spalten der Matrix, daher

[4]:
j = 0

Gesucht ist daher die Zeile mit dem grössten absoluten Wert der ersten Spalte:

[5]:
indPivot = j
for i in range(j,n):
    if np.abs(R[ind[i],j]) >= np.abs(R[ind[indPivot],j]):
        indPivot = i
indPivot
[5]:
2

das geht auch kurz und viel schneller mit:

[6]:
j+np.argmax(abs(R[ind[j:],j]))
[6]:
2

Die Reihenfolge wird nun aktualisiert:

[7]:
i = ind[indPivot]  # Wert sichern
ind[indPivot] = ind[j] # neue Reihenfolge
ind[j] = i
ind
[7]:
array([2, 1, 0, 3])

Über die restlichen Zeilen (bezogen auf die Einträge im Vektor ind) wird die Gauss Elimination (Algorithmus 2.2) angewandt:

[8]:
for i in range(j+1,n):
    lij = R[ind[i],j]/R[ind[j],j]
    L[ind[i],j] = lij    # speichern der unteren Dreiecksmatrix
    R[ind[i],j:] -= lij*R[ind[j],j:] # bearbeitet die Zeile ind(i)
R
[8]:
array([[ 0.        , -1.33333333, -2.66666667,  1.        ],
       [ 0.        , -0.66666667, -2.33333333, -3.        ],
       [ 6.        ,  1.        , -1.        ,  6.        ],
       [ 0.        , -4.66666667,  3.66666667,  3.        ]])

In der neuen Ordnung:

[9]:
R[ind,:]
[9]:
array([[ 6.        ,  1.        , -1.        ,  6.        ],
       [ 0.        , -0.66666667, -2.33333333, -3.        ],
       [ 0.        , -1.33333333, -2.66666667,  1.        ],
       [ 0.        , -4.66666667,  3.66666667,  3.        ]])

Zweite Spalte

Procedere für die zweite Spalte

[10]:
j = 1

Pivotelement bestimmen:

[11]:
ind
[11]:
array([2, 1, 0, 3])
[12]:
indPivot = j+np.argmax(abs(R[ind[j:],j]))
indPivot
[12]:
3

Die Reihenfolge wird nun aktualisiert (kompakt geschrieben):

[13]:
ind[indPivot], ind[j] = ind[j], ind[indPivot] # neue Reihenfolge
ind
[13]:
array([2, 3, 0, 1])

Gauss Elimination (Algorithmus 2.2):

[14]:
for i in range(j+1,n):
    lij = R[ind[i],j]/R[ind[j],j]
    L[ind[i],j] = lij    # speichern der unteren Dreiecksmatrix
    R[ind[i],j:] -= lij*R[ind[j],j:] # bearbeitet die Zeile ind(i)
R
[14]:
array([[ 0.        ,  0.        , -3.71428571,  0.14285714],
       [ 0.        ,  0.        , -2.85714286, -3.42857143],
       [ 6.        ,  1.        , -1.        ,  6.        ],
       [ 0.        , -4.66666667,  3.66666667,  3.        ]])

In der neuen Ordnung:

[15]:
R[ind,:]
[15]:
array([[ 6.        ,  1.        , -1.        ,  6.        ],
       [ 0.        , -4.66666667,  3.66666667,  3.        ],
       [ 0.        ,  0.        , -3.71428571,  0.14285714],
       [ 0.        ,  0.        , -2.85714286, -3.42857143]])

Dritte Spalte

Procedere für die dritte Spalte

[16]:
j = 2
[17]:
indPivot = j+np.argmax(abs(R[ind[j:],j]))
indPivot
[17]:
2

Die Reihenfolge wird nun aktualisiert:

[18]:
ind
[18]:
array([2, 3, 0, 1])
[19]:
ind[j]
[19]:
0
[20]:
ind[indPivot], ind[j] = ind[j], ind[indPivot] # neue Reihenfolge
ind
[20]:
array([2, 3, 0, 1])

Gauss Elimination (Algorithmus 2.2):

[21]:
for i in range(j+1,n):
    lij = R[ind[i],j]/R[ind[j],j]
    L[ind[i],j] = lij    # speichern der unteren Dreiecksmatrix
    R[ind[i],j:] -= lij*R[ind[j],j:] # bearbeitet die Zeile ind(i)
R
[21]:
array([[ 0.        ,  0.        , -3.71428571,  0.14285714],
       [ 0.        ,  0.        ,  0.        , -3.53846154],
       [ 6.        ,  1.        , -1.        ,  6.        ],
       [ 0.        , -4.66666667,  3.66666667,  3.        ]])

In der neuen Ordnung:

[22]:
R[ind,:]
[22]:
array([[ 6.        ,  1.        , -1.        ,  6.        ],
       [ 0.        , -4.66666667,  3.66666667,  3.        ],
       [ 0.        ,  0.        , -3.71428571,  0.14285714],
       [ 0.        ,  0.        ,  0.        , -3.53846154]])

Damit ist die Faktorisierung abgeschlossen. Die aus der Pivotisierung erhaltene Reihenfolge der Zeilen ist gegeben durch

[23]:
ind
[23]:
array([2, 3, 0, 1])

Damit erhalten wir eine obere Dreiecksmatrix:

[24]:
R[ind,:]
[24]:
array([[ 6.        ,  1.        , -1.        ,  6.        ],
       [ 0.        , -4.66666667,  3.66666667,  3.        ],
       [ 0.        ,  0.        , -3.71428571,  0.14285714],
       [ 0.        ,  0.        ,  0.        , -3.53846154]])

und für die untere Dreiecksmatrix folgt nach Addition der Einheitsmatrix:

[25]:
L[ind,:]+= np.eye(n)

Kontrolle

[26]:
(L[ind]@(R[ind,:]))-A[ind]
[26]:
array([[ 0.00000000e+00,  0.00000000e+00,  0.00000000e+00,
         0.00000000e+00],
       [ 0.00000000e+00,  0.00000000e+00,  0.00000000e+00,
         0.00000000e+00],
       [ 0.00000000e+00,  0.00000000e+00,  0.00000000e+00,
         0.00000000e+00],
       [ 0.00000000e+00, -4.22942105e-17,  0.00000000e+00,
         0.00000000e+00]])

Damit das Handling praktisch bleibt, speichern wir \(L,R\) als untere und obere Dreiecksmatrix:

[27]:
L = L[ind,:]
R = R[ind,:]

Die Permutationsmatrix kann einfach erstellt werden:

[28]:
P = np.eye(n)[ind]
P
[28]:
array([[0., 0., 1., 0.],
       [0., 0., 0., 1.],
       [1., 0., 0., 0.],
       [0., 1., 0., 0.]])

Damit ist das Resultat gegeben durch:

[29]:
P, L, R
[29]:
(array([[0., 0., 1., 0.],
        [0., 0., 0., 1.],
        [1., 0., 0., 0.],
        [0., 1., 0., 0.]]),
 array([[ 1.        ,  0.        ,  0.        ,  0.        ],
        [-0.33333333,  1.        ,  0.        ,  0.        ],
        [ 0.33333333,  0.28571429,  1.        ,  0.        ],
        [ 0.66666667,  0.14285714,  0.76923077,  1.        ]]),
 array([[ 6.        ,  1.        , -1.        ,  6.        ],
        [ 0.        , -4.66666667,  3.66666667,  3.        ],
        [ 0.        ,  0.        , -3.71428571,  0.14285714],
        [ 0.        ,  0.        ,  0.        , -3.53846154]]))

Lösen der linearen Gleichung

Das Lösen der linearen Gleichung

\[A\cdot x = b\tag{*}\]

erfolgt nun mit Hilfe der unteren und oberen Dreickmatrix durch Vorwärts- bzw. Rückwärtseinsetzen (Algorithmus 3.1).

Es gilt

\[P\cdot A = L\cdot R\]

somit folgt

\[L\cdot R\cdot x = P\cdot b.\]

Mit \(z = R\cdot x\) erhalten wir für (*) die beiden linearen Gleichungen

\[\begin{split}\begin{split} L\cdot z & = P\cdot b \quad\Rightarrow\ \text{lösen durch Vorwärtseinsetzen}\\ R\cdot x & = z \quad\Rightarrow\ \text{lösen durch Rückwärtseinsetzen}.\end{split}\end{split}\]

Rechte Seite für das Problem:

[30]:
b = np.array([1, -8, -16, -12])
b
[30]:
array([  1,  -8, -16, -12])

Vorwärtseinsetzen

[31]:
z = np.zeros(n)
z[0] = b[ind[0]]
for i in range(1,n):
    z[i] = b[ind[i]] - L[i,:i]@z[:i]
z
[31]:
array([-16.        , -17.33333333,  11.28571429,  -3.53846154])

Vorsicht: Das Vorgehen setzt eine normierte Matrix \(L\) voraus!

Rückwärtseinsetzen

[32]:
x = np.zeros(n)
x[n-1] = z[n-1]/R[n-1,n-1]
for i in range(2,-1,-1):
    x[i] = (z[i]-R[i,i+1:]@x[i+1:])/R[i,i]
x
[32]:
array([-4.5,  2. , -3. ,  1. ])

Bemerkung: Die Reihenfolge im Vektor z entspricht nicht der Reihenfolge der Zeilen! Da der Vektor nur ein Zwischenresultat enthält, hat dies keinen Einfluss.

Residuum: \(\|A\cdot x -b\|\)

[33]:
np.linalg.norm(A@x-b)
[33]:
1.831026719408895e-15

Python lu Methode in Scipy

In Python steht uns die LR-Zerlegung in scipy der Erweiterung von numpy zur Verfügung:

[34]:
from scipy.linalg import lu
[35]:
help(lu)
Help on function lu in module scipy.linalg.decomp_lu:

lu(a, permute_l=False, overwrite_a=False, check_finite=True)
    Compute pivoted LU decomposition of a matrix.

    The decomposition is::

        A = P L U

    where P is a permutation matrix, L lower triangular with unit
    diagonal elements, and U upper triangular.

    Parameters
    ----------
    a : (M, N) array_like
        Array to decompose
    permute_l : bool, optional
        Perform the multiplication P*L (Default: do not permute)
    overwrite_a : bool, optional
        Whether to overwrite data in a (may improve performance)
    check_finite : bool, optional
        Whether to check that the input matrix contains only finite numbers.
        Disabling may give a performance gain, but may result in problems
        (crashes, non-termination) if the inputs do contain infinities or NaNs.

    Returns
    -------
    **(If permute_l == False)**

    p : (M, M) ndarray
        Permutation matrix
    l : (M, K) ndarray
        Lower triangular or trapezoidal matrix with unit diagonal.
        K = min(M, N)
    u : (K, N) ndarray
        Upper triangular or trapezoidal matrix

    **(If permute_l == True)**

    pl : (M, K) ndarray
        Permuted L matrix.
        K = min(M, N)
    u : (K, N) ndarray
        Upper triangular or trapezoidal matrix

    Notes
    -----
    This is a LU factorization routine written for SciPy.

    Examples
    --------
    >>> from scipy.linalg import lu
    >>> A = np.array([[2, 5, 8, 7], [5, 2, 2, 8], [7, 5, 6, 6], [5, 4, 4, 8]])
    >>> p, l, u = lu(A)
    >>> np.allclose(A - p @ l @ u, np.zeros((4, 4)))
    True

Wir erhalten hier exakt obiges Resultat mit der Permutationsmatrix:

[36]:
Ps, Ls, Rs = lu(A)
Ps, Ls, Rs
[36]:
(array([[0., 0., 1., 0.],
        [0., 0., 0., 1.],
        [1., 0., 0., 0.],
        [0., 1., 0., 0.]]),
 array([[ 1.        ,  0.        ,  0.        ,  0.        ],
        [-0.33333333,  1.        ,  0.        ,  0.        ],
        [ 0.33333333,  0.28571429,  1.        ,  0.        ],
        [ 0.66666667,  0.14285714,  0.76923077,  1.        ]]),
 array([[ 6.        ,  1.        , -1.        ,  6.        ],
        [ 0.        , -4.66666667,  3.66666667,  3.        ],
        [ 0.        ,  0.        , -3.71428571,  0.14285714],
        [ 0.        ,  0.        ,  0.        , -3.53846154]]))
[37]:
ind
[37]:
array([2, 3, 0, 1])
[ ]: