Die numerische Lösung von Differentialgleichungen (DG) kann schrittweise unter Nutzung der örtlichen Steigungswerte erfolgen, die durch die DG in der Form y'=f(x,y) gegeben sind.
Im einfachsten Falle berechnet man, beginnend bei einem Startwert xo, yo = y(xo) mit einem kleinen Schritt dx in x-Richtung einen Zielwert y(xo+dx) = yo + y'(xo) * dx. Geometrisch beschrieben: Es wird für die Lösung ein Abschnitt der Tangente genutzt, welche die Lösungskurve im Startpunkt berührt. Anschließend nimmt man diesen berechneten Punkt als Startwert einer zweiten Berechnung etc.
Dieses Verfahren trägt den Namen Leonhard Eulers (1707-1783) und tut ihm keine Ehre, denn es ergibt Lösungen, die mit zunehmender Schrittzahl systematisch von der wahren Lösung abweichen.
Natürlich würde ich dies hier jetzt nicht so ausdrücken, wenn es nicht bessere Lösungen gäbe:
Das Runge-Kutta-Verfahren vermeidet den Nachteil des Eulerverfahrens, indem es bei der Auswertung nicht linear, sondern mit einem Polynom höheren als ersten Grades extrapoliert. Dazu gibt es mehrere Varianten, ich stelle hier die verbreitetste, das "Runge-Kutta-Verfahren vierter Ordnung" vor. Der Grundalgorithmus mit den Hilfsvariablen a-d und der Intervallänge dx lautet wie folgt:
| Function Funk (x As Single, y As Single) As Single Funk = f (x, y) 'hier die Formel in Basicnotation einsetzen End FunctionSub Runge_Kutta (xvon As Single, xbis As single, yvon As Single) 'Startwerte x = xvon y = yvon 'Berechnung für ein Intervall Do a = Funk ( x, y ) b = Funk ( x + dx / 2, y + dx * a / 2 ) c = Funk ( x + dx / 2, y + dx * b / 2 ) d = Funk ( x + dx, y + dx * c ) 'neue Werte am Intervallende y = y + dx * ( a + 2 * b + 2 * c + d ) / 6 x = x + dx 'Ausgabe Printer.Print x, y loop until x >= xbis 'Grenze des auszuwertenden Bereiches erreicht End Sub |
Ein Nachteil der oben gezeigten einfachen Variante ist es, daß die Schrittweite dx für die ganze Auswertung den gleichen Wert besitzt. Man kann sich vorstellen, daß in Bereichen, in denen die Ableitung schwach veränderlich ist, große Schrittweiten, in denen mit hoher Veränderlichkeit dagegen kleine Schrittweiten angemessen wären.
Wenn wir schon einen Computer nutzen, soll er auch was tun und darüber von sich aus nachdenken! Das geschieht nicht in der angedeuteten Weise, vielmehr soll die Schrittweite vom Fehler derAnpassung gesteuert werden.
Die Idee ist, daß man zu einem Auswertungsintervall ddx prüft, ob der errechnete Wert yd über diese Länge von demjenigen abweicht, den man bei halbiertem Intervall und demgemäß nach zwei Schritten erhält, er heiße yd1. Solche Intervallteilung wird so lange fortgesetzt, bis die absolute Abweichung einen zuvor festgelegten kleinen Wert eps unterschreitet. Danach wird in der erforderlichen Schrittzahl der Funktionsverlauf innerhalb des gesamten dx-Intervalls berechnet. Erst der Endwert wird wieder ausgegeben.
Aus praktischen Gründen werden Routinen für die Ein-Schritt-( SubE_Runde) und die Mehrfach-Berechnung ( Sub M_Runde) ausgelagert. Dann sieht das Verfahren wie folgt aus:
| Sub Runge_Kutta (ByVal xvon As Double, ByVal xbis As Double, ByVal dx As Double, yvon As Double) Dim xd As Double, yd As Double, yd2 As Double Dim x As Double, y As Double, ddx As Double, Ausgabe AsString x = Xvon y = Yvon Ausgabe = "Runden" & " " & "X-Werte" & " " & "Y-Werte" & vbCrLf Ausgabe = " - " & " " & xvon & " " & yvon & vbCrLf Do ddx = dx Do xd = x yd = y E_Runde xd, yd, ddx Y_Zwi1 = yl xd = x yd1 = y ddx = 0.5 * ddx E_Runde xd, yd1, ddx E_Runde xd, yd1, ddx Loop Until Abs(yd - yd1) < eps xd = x yd = y Hl = h M_Runde x, x+dx, y, ddx x = xd y = yd Ausgabe = Ausgabe & dx / ddx & " " & x & " " & y & vbCrLf Loop Until x >= xbis End SubPrinter.Print Ausgabe 'Drucker oder was man sonst will Sub E_Runde (x As Double, y As Double, ByVal dx As Double) Dim a As Double, b As Double, c As Double, d As Double, ddx As Double End Subddx = 0.5 * dx 'Rechenaufwand senken! a = Funk (x, y) b = Funk (x + ddx, y + a * ddx) c = Funk (x + ddx, y + b * ddx) d = Funk (x + dx, y + c * dx) y = y + dx * (a + 2 * b + 2 * c + d) / 6 x = x + dx Sub M_Runde (x As Double, ByVal xbis As Double, y As Double, ByVal dx As Double) Dim a As Double, b As Double, c As Double, d As Double, ddx As Double ddx = dx *0.5 'Rechenaufwand senken! While x < xbis - 0.5 * dx a = Funk (x, y) b = Funk (x + ddx, y + a * ddx) c = Funk (x + ddx, y + b * ddx) d = Funk (x + dx, y + c * h) y = y + dx * (a + 2 * b + 2 * c + d) / 6 x = x + dx Wend End Sub |
Eine Weiterentwicklung des Runge-Kutta-Verfahrens, das Runge-Kutta-Fehlberg-Verfahren (RKF), verbessert die Schrittweitensteuerung in dem Sinne, daß im Verfahren ein in etwa konstantes Fehlerniveau gehalten wird. Wem der folgende Code wie Hokus-Pokus vorkommt, der lese nach bei: Paul L. De Vries, Computerphysik, Spektrum Akademischer Verlag 1995. Dort wird der folgende Algorithmus ausführlich begründet.
| Sub RKF (ByVal xvon#, ByVal xbis#, ByVal yvon#) 'h und eps sind global vorgegeben! Dim x#, y#, xs#, ys# Dim i%, j% Dim F#(5), A#(5), B#(5, 4), C#(5), D#(5) Dim yhut#, Err#, MaxErr#, hNeu# 'Konstantensatz: Nicht verändern! A(1) = 0.25: A(2) = 0.375: A(3) = 12 / 13: A(4) = 1:A(5) = 0.5 B(1, 0) = 0.25 B(2, 0) = 3 / 32: B(2, 1) = 9 /32 B(3, 0) = 1932 / 2197: B(3, 1) = -7200 / 2197:B(3,2) = 7296 / 2197 B(4, 0) = 439 / 216: B(4, 1) = -8: B(4, 2) = 3680 / 513: B(4, 3) = -845 / 4104 B(5, 0) = -8 / 27: B(5, 1) = 2: B(5, 2) = -3544 / 2565: B(5, 3) = 1859 / 4104: B(5, 4) = -11 / 40 C(0) = 16 / 135: C(1) = 0: C(2) = 6656 / 12825: C(3) = 28561 / 56430: C(4) = -9 / 50: C(5) = 2 / 55 D(0) = 1 / 360: D(1) = 0: D(2) = -128 / 4275: D(3) = -2197 / 75240: D(4) = 1 / 50: D(5) = 2 / 55 'jetzt geht es los! xs = xvon: ys = yvon Do F(0) = Funk (xs, ys) Do For i = 1 To 5 x = xs + A(i) * h y = 0 For j = 0 To i - 1 y = y + B(i, j) * F(j) Next j y = h * y + ys F(i) = Funk (x, y) Next i yhut = 0: Err = 0 For i = 0 To 5 yhut = yhut + C(i) * F(i) Err = Err + D(i) * F(i) Next i yhut = h * yhut + ys Err = h * Abs(Err) MaxErr = h * Eps hNeu = 0.9 * h * Sqr(Sqr(MaxErr / Err)) If Err > MaxErr Then h = hNeu Loop Until Err <= MaxErr xs = xs + h ys = yhut 'das sind die neuen Werte, hier abgreifen! h = hNeu Loop Until xs >= xbis End Sub |
Systeme von DGn kommen in der Praxis oft vor. Eines der bekanntesten Systeme ist das von Lotka und Volterra formulierte Differentialgleichungssystem für ein Räuber-Beute-System. Die bisher vorgestellten Verfahren lassen sich leicht auf Systeme von DGn erster Ordnung übertragen.
Ein neues Problem bedeuten DGn höherer Ordnung. Daß sie hier mit behandelt werde, hat einen guten Grund. Diese Gleichungen lassen sich nämlich auf Systeme linearer DGn reduzieren.
Wie das genau geht?
Gegeben sei z.B. y" = f(x, y, y'); man setzt y1 = yund y2= y' und erhält zwei unbestreitbar richtige Gleichungen: y1'= y2; y2' = f(x, y1, y2). That's it! Was brauchen wir mehr?
Der Algorithmus verkompliziert sich insofern, als aus linearen Größen, wie y , jetzt Vektoren y(i) werden. Nur x bleibt skalar. Im Aufruf der folgenden Routine bedeutet num die Anzahl simultan zu lösender Gleichungen. Der Algorithmus sieht etwa so aus:
| Public Sub RKFN(ByVal xvon#, ByVal xbis#, ByVal yvon#(), ByVal num%) 'Global sind hmin, hmax, eps vorgegeben! Dim x#, y#(10), xs#, ys#(10), H#, yalt#(10) Dim i%, j%, k% Dim F#(5, 10), A#(5), B#(5, 4), C#(5), D#(5) Dim yhut#(10), Err#, MaxErr#, GrossErr#, HNeu# 'Konstantensatz: Nicht Verändern! A(1) = 0.25: A(2) = 0.375: A(3) = 12 / 13: A(4) = 1: A(5) = 0.5 B(1, 0) = 0.25 B(2, 0) = 3 / 32: B(2, 1) = 9 / 32 B(3, 0) = 1932 / 2197: B(3, 1) = -7200 / 2197: B(3,2) = 7296 / 2197 B(4, 0) = 439 / 216: B(4, 1) = -8: B(4, 2) = 3680 / 513: B(4, 3) = -845 / 4104 B(5, 0) = -8 / 27: B(5, 1) = 2: B(5, 2) = -3544 / 2565: B(5, 3) = 1859 / 4104: B(5, 4) = -11 / 40 C(0) = 16 / 135: C(1) = 0: C(2) = 6656 / 12825: C(3) = 28561 / 56430: C(4) = -9 / 50: C(5) = 2 / 55 D(0) = 1 / 360: D(1) = 0: D(2) = -128 / 4275: D(3) = -2197 / 75240: D(4) = 1 / 50: D(5) = 2 / 55 H = hmax For k = 1 To num yalt(k) = ys(k) Next xs = xvon For k = 1 To num ys(k) = yvon(k) Next Do Funktion xs, ys(), F(), 0 Do For i = 1 To 5 x = xs + A(i) * H For k = 1 To num y(k) = 0 For j = 0 To i - 1 y(k) = y(k) + B(i, j) * F(j, k) Next j y(k) = H * y(k) + ys(k) Next k Funktion x, y(), F(), i Next i GrossErr = 0 For k = 1 To num yhut(k) = 0 Err = 0 For i = 0 To 5 yhut(k) = yhut(k) + C(i) * F(i, k) Err = Err + D(i) * F(i, k) Next yhut(k) = H * yhut(k) + ys(k) Err = H * Abs(Err) If Err > GrossErr Then GrossErr = Err Next k MaxErr = H * Eps HNeu = 0.9 * H*Sqr(Sqr(MaxErr / GrossErr)) If GrossErr > MaxErr Then H = HNeu Loop Until GrossErr <= MaxErr 'Werte am Intervallende xs = xs + H For k = 1 To num ys(k) = yhut(k) Next H = HNeu Loop Until xs >= xbis End Sub |
Die Funktionsroutine, hier "Funktion" genannt, enthält die Differentialgleichungen und berechnet an der Stelle des x-Wertes und der diversen y-Werte den Satz der Funktionswerte y(i).