Lineare Gleichungssysteme

Gaußscher Algorithmus | Householder | Matrizeninversion
Gauß-Seidel-Iteration | Verfahrenstest

Gauß-Elimination

Gedachte Darstellung des Gleichungssystems ist:

a11* x1+ a12* x2+ ... + a1n* xn= b1
...bild ...

an1* x1+ an2* x2+ ... + ann* xn= bn

Die Koeffizienten aij des Systems aus n Gleichungen mit n Unbekannten liegen im Array A(1..n, 1..n), die rechten Seiten der Gleichung bi sind als (n+1)te Spalte in das Array A() aufgenommen. Die Lösung wird in den Vektor X(1..n) geschrieben. S ist eine Hilfsgröße (vom Typ Single), i, j, k sind Integer.

Bei diesem Algorithmus werden Spalte für Spalte (Zähler ist k) die jeweils unterhalb der Hauptdiagonalen stehenden Glieder eliminiert, indem von der zu behandelnden Gleichung ein geeignetes Vielfaches der k-ten Gleichung abgezogen wird. Es entsteht eine Dreiecksmatrix, die anschließend rückwärts aufgelöst wird.

'Beginn Gleichungs-System
For k = 1 To n - 1
For i = k + 1 To n
S = A(i, k) / A(k, k)
A(i, k) = 0
For j = k + 1 To n + 1
A(i, j) = A(i, j) - S * A(k, j)
Next
Next
Next
X(n) = A(n, n + 1) / A(n, n)
For i = n - 1 To 1 Step -1
S = A(i, n + 1)
For j = i + 1 To n
S = S - A(i, j) * X(j)
Next
X(i) = S / A(i, i)
Next
'Ende Gleichungs-System

Es handelt sich hier noch um den "Roh-Algorithmus", denn die Divisionen durch A(k,k) könnten schiefgehen (bei A(k,k)=0). Das würde den Programmabbruch zur Folge haben. An dieser Stelle ist eine Routine zum Auffangen des Fehlers erforderlich, weil eine Meldung allemal besser als ein Fehlerabbruch ist. Der Einbau von Fehlerroutinen gehört beim Hobbyprogrammierer eher zur "Schönheitspflege", im professionellen Bereich ist er natürlich Pflicht.



Kleine Nachbesserung

Wenn das GS nicht lösbar ist, was wir mal außen vor lassen wollen, kann das Verfahren ungenau werden oder, trotz Lösbarkeit versagen, wenn eines der Hauptdiagonalelemente der Matrix A(i,j) zu klein ist oder im Zuge der Elimination zu klein wird oder gar verschwindet.
Das Zauberwort heißt "Pivotsuche".
Man bestimmt vor jeder Eliminationsrunde das größte in Frage kommende Element der Restmatrix für die Division. Damit unter den Gleichungen die Chancen gleichmäßig verteilt sind, wird zuvor "normalisiert". Für die theoretischen Überlegungen dazu lese man die einschlägige Literatur. (Einschlägig heißt hier, daß das Lesen so viel Freude macht, wie eine Tracht Prügel zu empfangen).
Es folgt eine komplette Prozedur, n ist der Grad des Gleichungssystems, A () ist wie im oberen Beispiel belegt (Achtung, der Inhalt von A() wird verändert!), in x() wird die Lösung zurückgeliefert.

Sub gauss(n%, A() As Double, x() As Double)
Dim i%, j%, k%, jmax%, kmax%, merk() As Integer
Dim s As Double, max As Double, skal() As Double
ReDim merk(n), skal(n)
'Reihenfolge sichern
For i = 1 To n
merk(i) = i
Next
'Normalisierung
For i = 1 To n
s = 0
For j = 1 To n
s = s + Abs(A(i, j))
Next
skal(i) = 1 / s
Next
'Vorwärtselimination
For k = 1 To n - 1
max = skal(k) * Abs(A(k, k))
kmax = k 'Spalte mit max
jmax = k 'Zeile mit max
For j = k To n 'Pivotzelle suchen
For i = k To n
If skal(j) * Abs(A(j, i))> max Then
jmax = j
kmax = i
max = skal(j) * Abs(A(j, i))
End If
Next
Next
If jmax <> k Then 'Zeilentausch, wenn nötig
For j = k To n + 1
s = A(k, j)
A(k, j) = A(jmax, j)
A(jmax, j) = s
Next
s = skal(k)
skal(k) = skal(jmax)
skal(jmax) = s
End If
If kmax <> k Then 'Spaltentausch, wenn nötig
For i = 1 To n
s = A(i, k)
A(i, k) = A(i, kmax)
A(i, kmax) = s
Next
j = merk(k)
merk(k) = merk(kmax)
merk(kmax) = j
End If
'eigentliche Elimination
For i = k + 1 To n
s = A(i, k) / A(k, k)
A(i, k) = 0#
For j = k + 1 To n + 1
A(i, j) = A(i, j) - s * A(k, j)
Next
Next
Next
'Auflösung rückwärts
x(merk(n)) = A(n, n + 1) / A(n, n)
For i = n - 1 To 1 Step -1
s = A(i, n + 1)
For j = i + 1 To n
s = s - A(i, j) * x(merk(j))
Next
x(merk(i)) = s / A(i, i)
Next
End Sub
zum Anfang

Householder-Algorithmus

Wenn die Koeffizientenmatrix des GS symmetrisch ist, transformiert das Householder-Verfahren diese in tridiagonale Form, die sich dann leicht auflösen läßt. Der Vorteil ist hohe numerische Stabilität des Verfahrens; der Rechenaufwand etwa doppelt so hoch, wie beim Gauß-Verfahren.

Sub Householder(n%, A() As Double, X() As Double)
Dim i%, j%, k%
Dim S#, T#, hlp#(20)
'Transformation der Koeffizientenmatrix
For j = 1 To n
S = 0
For i = j To n
S = S + A(i, j) ^ 2
Next
If S <> 0 Then
If A(j, j) <0 Then hlp(j) = Sqr(S) Else hlp(j) = -Sqr(S)
T = 1 / (hlp(j) * A(j, j) - S)
A(j, j) = A(j, j) - hlp(j)
For k = j + 1 To n + 1
S = 0
For i = j To n
S = S + A(i, j) * A(i, k)
Next
S = S * T
For i = j To n
A(i, k) = A(i, k) + A(i, j) * S
Next
Next
Else
MsgBox "Householder wird abgebrochen"
Exit Sub
End If
Next
'Auflösung
X(n) = A(n, n + 1) / hlp(n)
For i = n - 1 To 1 Step -1
S = 0
For j = i + 1 To n
S = S + A(i, j) * X(j)
Next
X(i) = (A(i, n + 1) - S) / hlp(i)
Next
End Sub
zum Anfang

Matrizeninversion

Beispiel für einen komplexeren mathematischen Algorithmus (wer ihn braucht, weiß wozu!), dynamische Variable, Prozeduraufrufe mit Variablenübergabe, Dimensionierung.
Es werden zunächst nur die Variablen für die Matrix und den Rang der Matrix deklariert. Dann folgt die eigentliche Matrixinversion. Sie benötigt eine kleine Tauschroutine, die ebenfalls gezeigt wird.

Option Explicit
Dim MatrixA() As Double, N As Integer


Sub MatInv(Mat() As Double, N As Integer)
Dim Hlp1() As Double, Hlp2() As Double, Hlp3() As Double
ReDim Hlp1(N), Hlp2(N), Hlp3(N)
Dim Max As Double, T As Double
Dim i As Integer, j As Integer, k As Integer, ix As Integer, iy As Integer
'geht los
For i = 1 To N
Hlp3(i) = 0
Next
For i = 1 To N
'welches ist das größte Element
Max = 0
For j = 1 To N
If Hlp3(j)<>1 Then
For k = 1 To N
If Hlp3(k)<>1 And Max<= Abs(Mat(j, k)) Then
iy = k
ix = j
Max = Abs(Mat(j, k))
End If
Next
End If
Next
Hlp3(iy) = Hlp3(iy) + 1
'Pivotisierung
If ix<>iy Then
For j = 1 To N
Tausche Mat(ix, j), Mat(iy, j)
Next
End If
Hlp1(i) = ix
Hlp2(i) = iy
'Kontrolle auf mögliches Verschwinden eines Hauptachsenwertes
If Abs(Mat(iy, iy))<1E-300 Then
MsgBox "Abbruch, Inversion nicht möglich!"
Exit Sub
Else
T = Mat(iy, iy)
Mat(iy, iy) = 1
For j = 1 To N
Mat(iy, j) = Mat(iy, j) / T
Next
For j = 1 To N
If j<>iy Then
T = Mat(j, iy)
Mat(j, iy) = 0
For k = 1 To N
Mat(j, k) = Mat(j, k) - Mat(iy, k) * T
Next
End If
Next
End If
Next
'Rücktausch
For i = 1 To N
j = N + 1 - i
If Hlp1(j)<>Hlp2(j) Then
ix = Hlp1(j)
iy = Hlp2(j)
For k = 1 To N
Tausche Mat(k, ix), Mat(k, iy)
Next
End If
Next
'Hilfsspeicher freigeben
ReDim Hlp1(0), Hlp2(0), Hlp3(0)
'Ausgabe ....
End Sub
Private Sub Tausche(A As Double, B As Double)
Dim T As Double
T = A
A = B
B = T
End Sub

Das war es schon. Und was macht man mit einer Routine allein? Nicht mal testen kann man sie. Deshalb brauchen wir einen Aufruf, der beispielsweise eine Testmatrix mit Zufallszahlen belegt. Ob unsere Matrixinversion richtig rechnet, kann man bei der Gelegenheit ebenfalls testen, denn die Inverse der Inversen muß wieder die Ausgangsmatrix ergeben.

Sub MatrixAufruf(N As Integer)
Dim i As Integer, j As Integer, k As Long
Dim MatrixB() As Double, Max As Double, erg As String
ReDim MatrixA(N, N), MatrixB(N, N)
For i = 1 To N
For j = 1 To N
MatrixA(i, j) = (Rnd - 0.5) * 1000000
MatrixB(i, j) = MatrixA(i, j)
'erg = erg & Format(MatrixB(i, j), "Scientific") & " "
Next
'erg = erg & vbCrLf
frmDocument.txtText.Text = i
Next
'frmDocument.txtText.Text = erg
MatInv MatrixA, N
MatInv MatrixA, N
Max = -1
For i = 1 To N
For j = 1 To N
If Abs(MatrixA(i, j) - MatrixB(i, j))>Max Then Max = Abs(MatrixA(i, j) - MatrixB(i, j))
'erg = erg & Format(MatrixA(i, j) - MatrixB(i, j), "Scientific") & " "
Next
'erg = erg & vbCrLf
Next
frmDocument.txtText.Text = erg & "Größter Fehler ist " & Max
ReDim MatrixB(0, 0)
End Sub

Damit sich niemand wundert: Die auskommentierten Zeilen bauen einen String auf, der die Matrix selbst sowie die Matrix der Differenzen anzeigt. Für die Anzeige dient ein Textfeld namens txtText.

Eine wichtige Anwendung der Matrizeninversion ist die Lösung von linearen Gleichungssystemen. Dieses Lösungsverfahren hat ja den immensen Vorteil, sofern man nur die invertierte Matrix kennt, besonders elegant zu sein. Denn wenn Ax = b das Gleichungssystem in vektorieller Schreibweise beschreibt, dann ist x = A-1Ax = A-1b bereits die Lösung. Deshalb:

Sub invmat(n%, a#(), x#())
Dim i%, j%
MatInv n, a
For i = 1 To n
x(i) = 0
For j = 1 To n
x(i) = x(i) + a(i, j) * a(j, n + 1)
Next
Next
End Sub
zum Anfang

Gauß-Seidel-Iteration

Iterative Verfahren zur Lösung linearer GS, von denen es gleich mehrere gibt, (Jacobi, Gauß-Seidel, SOR), beruhen auf der Überlegung, daß man das Gleichungssystem Ax = b (vektoriell geschrieben) schrittweise lösen kann, indem man, immer im Kreis,x(neu) = x(alt) - Korr rechnet, wobei Korr ein Korrekturvektor ist, der irgendwie aus der Differenz von Ax(alt) und b, also dem Fehler gerechnet wird, den der alte x-Vektor im GS erzeugt. Man beginnt mit geratenen Werten und wenn man einen guten Tag hat, dann verschwindet irgendwann das Korrekturglied. Das nennt man dann: "Die Iteration steht". Der absolute Glücksfall.

Die Gauß-Seidel-Iteration ist speziell für schlecht konditionierte GS geeignet. Sie reagiert zudem weniger empfindlich auf fehlerbehaftete Koeffizienten als das Gauß-Verfahren. Der Nachteil ist, daß natürlich die Rechenzeit erheblich größer ist, als bei direkten Algorithmen. Dazu vergleiche man den Test am Ende dieser Seite.

Sub gaussseidel(n%, A() As Double, X() As Double)
Dim xp(50) As Double
Dim S As Double, T As Double
Dim i%, j%, num As Long
'Beginn
For i = 1 To n
X(i) = 5
Next
Do
For i = 1 To n
S = 0
For j = 1 To i - 1
S = S + A(i, j) * xp(j)
Next
T = 0
For j = i + 1 To n
T = T + A(i, j) * X(j)
Next
xp(i) = (A(i, n + 1) - S - T) / A(i, i)
Next
S = 0
For i = 1 To n
S = S + (X(i) - xp(i)) ^ 2
X(i) = xp(i)
Next
num = num + 1
Loop Until Sqr(S / (n - 1)) <0.000000001
End Sub
zum Anfang

Vergleichender Test

Ein Vergleich der Leistungsfähigkeit der gezeigten Algorithmen ist deshalb nicht sehr sinnvoll, weil diese für unterschiedliche Anforderungen optimiert sind. Aber interessant ist es allemal. Ich wähle hier ein schwach konditioniertes Gleichungssystem, dessen Koeffizientenmatrix, die sog. "Hilbertmatrix" zudem symmetrisch ist.

Sub hilberttest()
Dim n%, i%, j%, S As Double
n = CInt(ComboBox1.Text)
'Hilbertmatrix
For i = 1 To n
For j = 1 To n
a(i, j) = 1 / (i + j - 1)
Next
Next
'rechte Seite, alle xi sind = 1
For i = 1 To n
S = 0
For j = 1 To n
S = S + a(i, j)
Next
a(i, n + 1) = S
Next
gauss n, a, x
holen n, a, b
householder n, a, x
holen n, a, b
invmat n, a, x
holen n, a, b
gaussseidel n, a, x
End Sub

Die Koeffizientenmatrix mußte jeweils neu geholt werden, weil die Verfahren das aufrufende Array überschreiben (Bei Arrays gibt es leider keinen ByVal-Aufruf!)


Ergebnisübersicht

zunächst 12 Gleichungen mit 12 Unbekannten

Nr. Gauß Householder Mat-Inv Gauß-Seidel
x(1)
x(2)
x(3)
x(4)
x(5)
x(6)
x(7)
x(8)
x(9)
x(10)
x(11)
x(12)
0,999999994913475
1,00000062404417
0,999980756840177
1,00025909806798
0,998113734897155
1,00825643413237
0,977035467010419
1,04155259194011
0,95126025095053
1,03573507962894
0,985120426843695
1,00268554425617
0,999999981271439
1,00000235714355
0,999926170299648
1,0010035471671
0,99265315218153
1,03225647967565
0,910156747342672
1,16261083542408
0,809349307254756
1,13965007672443
0,941925053242107
1,01046630497711
0,999999902345735
1,00000980577897
0,999618638306856
1,00371235609055
0,975866317749023
1,01568412780762
0,658493041992188
1,49558258056641
0,746086120605469
1,11670684814453
0,875782012939453
1,02040243148804
0,999992870207552
1,00026940394669
0,997637348470586
1,00746409295183
0,992370964945155
0,997052386525436
1,0045641868918
1,00519101642664
1,00019348251134
0,995085740433649
0,995346812324949
1,00483686666268
Dauer1,8 ms4,0 ms5,7 ms105791,2 ms

Wie es aussieht, ist die Welt in Ordnung. Aber, schon eine Variable mehr verändert die Situation erheblich. Der Versuch mit dreizehn Gleichungen und dreizehn Unbekannten zeigt, daß wir numerisch im Grenzbereich wandeln:

Nr.GaußHouseholderMat-InvGauß-Seidel
x(1)
x(2)
x(3)
x(4)
x(5)
x(6)
x(7)
x(8)
x(9)
x(10)
x(11)
x(12)
x(13)
0,999999901088455
1,00001526494606
0,999417188455215
1,00964824199888
0,913651150105523
1,46750641279923
-0,629554582462835
4,77757515620127
-4,88366788883986
7,08422633362237
-3,00530887108786
2,51914806053944
0,747343543557318
1,00000063647624
0,999899895473124
1,00387061860638
0,935354954008552
1,58225480251897
-2,16737414272584
12,0800145166388
-24,7566980532241
41,2042476314134
-40,646884153115
28,4548815894478
-9,42501394315991
2,73544626110988
1,00000015624209
0,999933034647256
1,00056873261929
0,985750913619995
1,27785873413086
0,65203857421875
7,06903076171875
0,9720458984375
10,156005859375
-9,7440185546875
2,6826171875
-4,45159912109375
1,73866271972656
0,999994798171067
1,0001798271541
0,998595392430279
1,00365606351211
0,998085749933472
0,996527458325195
1,00042676605075
1,00347757287926
1,0027448974333
0,999347081027708
0,996326354571645
0,996923450959265
1,00371889455586
Dauer2,1 ms4,7 ms7,0 ms215240,6 ms

Nur Gauß-Seidel hat die Fassung bewahrt; keine Kunst, dieses Verfahren ist ja speziell für diese Sorte Gleichungssystem gedacht. Da kann man die größere Rechenzeit schon tolerieren.

zum Anfang

© R. Hirte * 2003