Man kann systematisch bessere Verfahren zum numerischen Lösen von Differentialgleichungen entwickeln. Diese Systematik stammt von Runge (1895) und Kutta (1901) und wurde von Butcher (1963) noch etwas vereinfacht. Geben hier nur einen groben Überblick und schauen uns an, wie die in diesem Kontext auftretenden Butcher-Tableaus zu verstehen sind.
6.6.1Ziel¶
Zunächst ist zu klären, wie man einem Verfahren ansieht, dass es “besser” ist als die Euler-Verfahren.
Grundsätzlich ist man für gegebene Schrittanzahl am Gesamtfehler
interessiert in Abhängigkeit von der Schrittweite . Für die beiden Euler-Verfahren gilt
für kleine und eine Konstante . Besser ist
mit , da zum Beispiel für das Halbieren der Schrittweite den Fehler auf ein Viertel reduziert. Man nennt den Exponent auch Konvergenzordnung des Verfahrens.
Je höher die Konvergenzordnung, desto größer kann gewählt werden und desto geringer also der Rechenaufwandt.
6.6.2Idee¶
Zur Erhöhung der Konvergenzordnung führt man Zwischenschritte ein, d.h. das Verfahren wertet an mehr Stellen aus und erhält so mehr Informationen über die Differentialgleichung. Dabei kann die Wahl der Stellen für die zusätzlichen Auswertungen auch von den Funktionswerten von abhängen.
Um geeignete Zwischenschritt zu finden, kann man aus der Forderung nach hoher Konvergenzordnung gewisse Gleichungen aufstellen. Das so entstehende nichtlineare Gleichungssystem muss dann symbolisch gelöst werden und liefert die unter behandelten Butcher-Tableaus. Man kann zeigen, dass beispielsweise für Konvergenzordnung mindestens 10 Zwischenschritte nötig sind, deren Berechnungsvorschriften aus der Lösung eines nichtlinearen Gleichungssystems mit 200 Gleichungen gewonnen werden.
6.6.3Heun-Verfahren¶
Als einfaches Verfahren mit einem Zwischenschritt betrachten wir kurz das Heun-Verfahren. Dieses führt zunächst einen Schritt des expliziten Euler-Verfahrens aus, um eine Näherung der Ableitung zu erhalten. Anschließend führt es den eigentlichen Schritt aus, wobei der verwendete Anstieg gerade der Mittelwert aus und ist:
Das Heun-Verfahren hat Konvergenzordnung und liefert damit bei gleicher Schrittweite genauere Ergebnisse.
6.6.4Allgemeine Formulierung¶
Die ein Runge-Kutta-Verfahren mit Zwischenschritten beschreibenden Zahlen werden üblicherweise als sogenanntes Butcher-Tableau geschrieben.
Die Iterationsvorschrift für das zugehörige Verfahren ist dann
wobei die Werte nach jedem Schritt verworfen werden können (und somit keinen Index tragen).
Ein Runge-Kutta-Verfahren mit Zwischenschritten wird auch als -stufiges Runge-Kutta-Verfahren bezeichnet.
6.6.5Implizite Runge-Kutta-Verfahren¶
Bei der hier formulierten Variante handelt es sich um explizite Runge-Kutta-Verfahren, da alle Schritt direkt berechnet werden können. Es gibt jedoch auch implizite Runge-Kutta-Verfahren, bei denen als Lösung eines nichtlinearen Gleichungssystems entstehen. In diesem Fall hat das Butcher-Tableau keine Dreieckstruktur, sondern ist voll besetzt, also rechteckig.
Implizite Runge-Kutta-Verfahren haben bei gleichem üblicherweise eine höhere Konvergenzordnung als explizite Verfahren. Während bei expliziten Verfahren höchstens Kovergenzordnung möglich ist, kann man mit impliziten Verfahren die Konvergenzordnung erreichen.
6.6.6Klassisches Runge-Kutta-Verfahren¶
Als klassisches Runge-Kutta-Verfahren oder als RK4-Verfahren wird das explizite Runge-Kutta-Verfahren mit dem Butcher-Tableau
bezeichnet.
6.6.7Systeme¶
Analog zu den Euler-Verfahren kann das Runge-Kutta-Verfahren ohne Änderungen an den Formeln auf Systeme von Differentialgleichungen angewendet werden. Die Zwischenwerte sind dann Vektoren mit Komponenten, wobei die Anzahl der Gleichungen im System ist.
6.6.8Numerischer Vergleich¶
Die unterschiedliche Konvergenzordnung von Euler-, Heun- und klassischem Runge-Kutta-Verfahren kann man leicht numerisch nachvollziehen. Lösen dazu das Anfangswertproblem
Wählen die Schrittweiten so, dass der Rechenaufwandt für die verschiedenen Verfahren in etwa gleich ist. Ist die Schrittweite für das Euler-Verfahren, so führen wir das Heun-Verfahren mit Schrittweite und das klassische Runge-Kutta-Verfahren mit Schrittweite aus.
Source
import matplotlib.pyplot as plt
import numpy as np
f = lambda x, y: np.sin(x) - y / x
x0 = 1
y0 = 1
x_max = x0 + 8 # Intervall, auf dem die Lösung berechnet werden soll
h_min = 0.5 # kleinste Schrittweite
# Euler
h = h_min
x = [x0]
y = [y0]
while x[-1] < x_max:
x_new = x[-1] + h
y_new = y[-1] + h * f(x[-1], y[-1])
x.append(x_new)
y.append(y_new)
x_euler = x
y_euler = y
# Heun
h = 2 * h_min
x = [x0]
y = [y0]
while x[-1] < x_max:
x_new = x[-1] + h
y_tilde = y[-1] + h * f(x[-1], y[-1])
y_new = y[-1] + 0.5 * h * (f(x[-1], y[-1]) + f(x_new, y_tilde))
x.append(x_new)
y.append(y_new)
x_heun = x
y_heun = y
# Runge-Kutta
h = 4 * h_min
x = [x0]
y = [y0]
while x[-1] < x_max:
x_new = x[-1] + h
z1 = f(x[-1], y[-1])
z2 = f(x[-1] + 0.5 * h, y[-1] + 0.5 * h * z1)
z3 = f(x[-1] + 0.5 * h, y[-1] + 0.5 * h * z2)
z4 = f(x[-1] + h, y[-1] + h * z3)
y_new = y[-1] + h * (1/6 * z1 + 1/3 * z2 + 1/3 * z3 + 1/6 * z4)
x.append(x_new)
y.append(y_new)
x_rk = x
y_rk = y
# Plot
fig, ax = plt.subplots(figsize=(10, 5))
sol_x = np.linspace(x0 - 0.5, x_max + 0.5, 100)
sol_y = (np.sin(sol_x) - sol_x * np.cos(sol_x) + 1 - np.sin(1) + np.cos(1)) / sol_x
ax.plot(sol_x, sol_y, '-r', label='exakte Lösung')
ax.plot(x_euler, y_euler, '-bo', label='Euler')
ax.plot(x_heun, y_heun, '-mo', label='Heun')
ax.plot(x_rk, y_rk, '-go', label='Runge-Kutta')
ax.plot(x0, y0, 'or', label='Anfangsbedingung')
ax.legend()
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.grid()
plt.show()