LU-faktorisering og Gauss-eliminasjon#
TMA4400 Matematikk 1: Kalkulus og lineær algebra
Dato: 01. september 2025
Eksempel#
Vi kan bruke standard Gauss-eliminasjon på følgende utvidede matrise
for å få
Om vi gjør det samme på
får vi
Matrisa \(A\) får altså den samme radreduserte formen, men vi må gjøre Gauss-elminiasjonen på nytt for hver av de to utvidede matrisene.
Finnes det en måte å lagre Gauss-eliminasjonen av \(A\) slik at vi ikke trenger å gjøre Gauss-elminiasjonen på nytt for hele systemet?
Først kan vi legge merke til at den radreduserte matrisa som svarer til \(A\),
er ei øvre triangulær matrise (elementene under diagonalen er \(0\)). Dette gjør at ligningssystemet til den radreduserte \((A|\mathbf{b})\) er veldig enkelt å løse siden vi raskt leser ut at
På samme måte er ligningssystemer i form av nedre triangulære matriser (elementene over diagonalen er \(0\)) også lette å løse.
LU-faktorisering#
La \(A\) være ei generell kvadratisk \(n\times n\)-matrise, \(\mathbf{x}\) en vektor i \(\mathbb{R}^n\) og \(\mathbf{b}\) en vektor i \(\mathbb{R}^n\). Vi skal nå løse følgende problem:
Anta at vi kan skrive \(A=LU\), der \(n\times n\)-matrisa \(L\) er nedre triangulær og \(n\times n\)-matrisa \(U\) er øvre triangulær. Da kan vi omformulere problemet på følgende måte:
Vi har altså byttet ut det å radredusere den utvidede matrisa \((A|\mathbf{b})\) med å løse to enkle triangulære ligningssystem \(L\mathbf{y}=\mathbf{b}\) og \(U\mathbf{x}=\mathbf{y}\). Fordelen er at vi nå ikke trenger å radredusere \((A|\mathbf{b})\) hver gang vi får gitt en ny \(\mathbf{b}\).
Men hvordan finner vi \(L\) og \(U\)? For å finne \(U\) velger vi rett og slett den radreduserte matrisa til \(A\).
import numpy as np
# Gauss-eliminasjon (antar at pivotelementene er forskjellig fra null i alle rader!)
def GE(B):
A = np.copy(B) # vi lager en kopi av den gitte matrisa B, for å unngå å endre denne
n,n = np.shape(B) # n=rader, n=kolonner; kvadratisk matrise
for k in range(0,n-1):
for j in range(k+1,n):
l = A[j,k]/A[k,k]
for p in range(k,n):
A[j,p] = A[j,p]-l*A[k,p]
U = A
return U
A0 = np.array([[1.,1.,1.],[4.,3.,-1.],[3.,5.,3.]])
U0 = GE(A0)
print('\nVi begynner med \n{0}\nog bruker Gauss-eliminasjon som gitt over: \n{1}'.format(np.matrix(A0),np.matrix(U0)))
Vi begynner med
[[ 1. 1. 1.]
[ 4. 3. -1.]
[ 3. 5. 3.]]
og bruker Gauss-eliminasjon som gitt over:
[[ 1. 1. 1.]
[ 0. -1. -5.]
[ 0. 0. -10.]]
For å få en entydig bestemt måte å skrive \(A=LU\) velger vi at \(L\) skal ha en diagonal med bare \(1\)-ere. Det gjenstår altså å bestemme elementene som befinner seg under diagonalen.
Hvis vi tar utgangspunkt i eksempelet over, skal vi altså finne
Legg merke til at \(L\) er slik at \(A=LU\), og \(U\) er den radreduserte matrisa. Da må \(L\) sine gjenværende elementer ha en verdi som kommer fra radreduseringen. Vi modifiserer koden på følgende måte:
import numpy as np
# Gauss-eliminasjon (antar at pivotelementene er forskjellig fra null i alle rader!)
def GE(B):
A = np.copy(B) # vi lager en kopi av den gitte matrisa B, for å unngå å endre denne
n, n = np.shape(B) # n=rader, n=kolonner; kvadratisk matrise
L = np.eye(n) # L begynner som identitetsmatrisa, og så fyller vi inn elementene under diagonalen
for k in range(n-1):
for j in range(k+1,n):
L[j,k] = A[j,k]/A[k,k] # vi lagrer multipliseringen i L
for p in range(k,n):
A[j,p] = A[j,p]-L[j,k]*A[k,p]
U = A
return L, U
A1 = np.array([[1.,1.,1.],[4.,3.,-1.],[3.,5.,3.]])
L1, U1 = GE(A1)
print('\nVi begynner med \n{0}\nog ender opp med følgende matriser: \n{1}\n{2}'.format(np.matrix(A1),np.matrix(L1),np.matrix(U1)))
Vi begynner med
[[ 1. 1. 1.]
[ 4. 3. -1.]
[ 3. 5. 3.]]
og ender opp med følgende matriser:
[[ 1. 0. 0.]
[ 4. 1. 0.]
[ 3. -2. 1.]]
[[ 1. 1. 1.]
[ 0. -1. -5.]
[ 0. 0. -10.]]
Vi har altså at
La oss teste dette:
L2 = np.array([[1.,0.,0.],[4.,1.,0.],[3.,-2.,1.]])
U2 = np.array([[1.,1.,1.], [0.,-1.,-5.],[0.,0.,-10.]])
A2 = np.dot(L2,U2) # notasjonen for matrisemultiplikasjon i python
print(A2)
[[ 1. 1. 1.]
[ 4. 3. -1.]
[ 3. 5. 3.]]
Numerisk ustabilitet#
La oss gjøre \(LU\)-faktorisering på følgende matrise:
A3 = np.array([[1.e-12,4.,1.],[2.,-1.,-2.],[1.,3.,2.]])
L3, U3 = GE(A3)
print('\nVi begynner med \n{0}\nog ender opp med følgende matriser: \n{1}\n{2}'.format(np.matrix(A3),np.matrix(L3),np.matrix(U3)))
Vi begynner med
[[ 1.e-12 4.e+00 1.e+00]
[ 2.e+00 -1.e+00 -2.e+00]
[ 1.e+00 3.e+00 2.e+00]]
og ender opp med følgende matriser:
[[1.e+00 0.e+00 0.e+00]
[2.e+12 1.e+00 0.e+00]
[1.e+12 5.e-01 1.e+00]]
[[ 1.000e-12 4.000e+00 1.000e+00]
[ 0.000e+00 -8.000e+12 -2.000e+12]
[ 0.000e+00 0.000e+00 2.125e+00]]
Vi tester så om vi får riktig produkt:
L4 = np.array([[1.,0.,0.],[2.e+12,1.,0.],[1.e+12,0.5,1.]])
U4 = np.array([[1.e-12,4.,1.], [0.,-8.e+12,-2.e+12],[0.,0.,2.125]])
A4 = np.dot(L4,U4) # notasjonen for matrisemultiplikasjon i python
print(A4)
[[1.000e-12 4.000e+00 1.000e+00]
[2.000e+00 0.000e+00 0.000e+00]
[1.000e+00 0.000e+00 2.125e+00]]
Vi ser at matrisa vi ender opp med,
ikke er den samme som vi begynte med. Vi trenger altså en forbedret algoritme.
LU-faktorisering med delvis pivotering#
Vi tar utgangspunkt i det vi gjorde med Gauss-eliminasjon, nemlig delvis pivotering. Vi gjenbruker koden og gjør de nødvendige endringene.
# Gauss-eliminasjon med delvis pivotering
def GEPP(B):
A = np.copy(B).astype(float) # vi lager en kopi av den gitte matrisa B, for å unngå å endre denne. i tillegg konverterer vi til desimaltall
# A = np.copy(B) # prøv gjerne uten for å se hva som skjer
n,n = np.shape(A) # n=rader, n=kolonner; kvadratisk matrise
L = np.eye(n) # L begynner som identitetsmatrisa, og så fyller vi inn elementene under diagonalen
for k in range(0,n-1):
a = np.abs(A[k,k])
pk = k
for j in range(k+1,n):
if a < np.abs(A[j,k]):
a = np.abs(A[j,k])
pk = j
if pk != k:
for p in range(k,n): # vi må bytte radene i A
r = A[k,p]
A[k,p] = A[pk,p]
A[pk,p] = r
for p in range(0,k): # vi må bytte de aktuelle radelementene i L
r = L[k,p]
L[k,p] = L[pk,p]
L[pk,p] = r
for j in range(k+1,n):
L[j,k] = A[j,k]/A[k,k]
for p in range(k,n):
A[j,p] = A[j,p]-L[j,k]*A[k,p]
U = A
return L, U
Igjen prøver vi oss på \(LU\)-faktorisering av følgende matrise:
A5 = np.array([[1.e-12,4.,1.],[2.,-1.,-2.],[1.,3.,2.]])
L5, U5 = GEPP(A5)
print('\nVi begynner med \n{0}\nog ender opp med følgende matriser: \n{1}\n{2}'.format(np.matrix(A5),np.matrix(L5),np.matrix(U5)))
Vi begynner med
[[ 1.e-12 4.e+00 1.e+00]
[ 2.e+00 -1.e+00 -2.e+00]
[ 1.e+00 3.e+00 2.e+00]]
og ender opp med følgende matriser:
[[1.00e+00 0.00e+00 0.00e+00]
[5.00e-13 1.00e+00 0.00e+00]
[5.00e-01 8.75e-01 1.00e+00]]
[[ 2. -1. -2. ]
[ 0. 4. 1. ]
[ 0. 0. 2.125]]
Vi tester igjen om vi får riktig produkt:
L6 = np.array([[1.,0.,0.],[5.e-13,1.,0.],[1/2.,0.875,1.]])
U6 = np.array([[2.,-1.,-2.], [0.,4.,1.],[0.,0.,17/8.]])
A6 = np.dot(L6,U6) # notasjonen for matrisemultiplikasjon i python
print(A6)
[[ 2.e+00 -1.e+00 -2.e+00]
[ 1.e-12 4.e+00 1.e+00]
[ 1.e+00 3.e+00 2.e+00]]
Vi får nå matrisa vi begynte med, men med rad 1 og 2 byttet om. Hvis vi hadde lagret permutasjonene som matrisa
ville vi fått at \(PA=LU\). Dette går vi ikke videre inn på her.