Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Betrachten das Lösen er Wellengleichung in einer Raumdimension mittels Finite-Differenzen-Verfahren genauer. Sei dazu B=[0,a]B=[0,a] das betrachtete Gebiet, sei [0,T][0,T] das betrachtete Zeitintervall und sei c>0c>0 die Ausbreitungsgeschwindigkeit der Wellen. Gesucht ist eine Funktion u:[0,a]×[0,T]Ru:[0,a]\times[0,T]\to\bbR, die

c2uxx(x,t)utt(x,t)=0c^2\,u_{xx}(x,t)-u_{tt}(x,t)=0

erfüllt. Eine Anfangsbedingung sei durch

u(x,0)=f(x),ut(x,0)=0u(x,0)=f(x),\qquad u_t(x,0)=0

mit f:[0,a]Rf:[0,a]\to\bbR gegeben.

8.1Randbedingungen

Wir betrachten verschiedene Randbedingungen:

Im ersten Fall soll also

u(0,t)=u(a,t)=0u(0,t)=u(a,t)=0

gelten.

Im zweiten Fall sollen keine Vertikalkräfte auf den Rand übertragen werden. Stellt man sich die Funktion uu als Verlauf einer Gitarrensaite vor, soll die Zugkraft also nur horizontal auf die Befestigung am Rand wirken. In vertikale Richtung ist die Befestigung frei beweglich. Erhaltene aus diesen Überlegungen

ux(0,t)=ux(a,t)=0.u_x(0,t)=u_x(a,t)=0.

Im dritten zu betrachtenden Fall soll die Welle am rechten Rand frei auslaufen können. Das Verhalten am Rand soll also so sein, als würde der betrachtete Bereich BB nach rechts beliebig groß sein, wir aber nur den Ausschnitt [0,a][0,a] sehen. Zusammen mit der Neumann-Bedingung für den linken Rand erhält man

ux(0,t)=0,ut(a,t)+cux(a,t)=0.u_x(0,t)=0,\quad u_t(a,t)+c\,u_x(a,t)=0.

Dass diese Randbedingung für unsere Zweck die richtige ist, sieht man, indem man u(x,t)=g(xct)u(x,t)=g(x-c\,t) für eine beliebige Funktion gg einsetzt. Diese Wahl von uu löst die Wellengleichung einschließlich Randbedinung am rechten Rand und entspricht einer nach rechts laufenden Welle (IDVID 810). Nach rechts laufende Wellen können also ungehindert den betrachteten Bereich verlassen. Verzichtet man auf diese Randbedingung, so ist die Lösung der Wellengleichung nicht mehr eindeutig bestimmt.

8.2Diskretisierung

8.2.1Gitterpunkte

Setzen

Δx:=anx,Δt:=Tnt\Delta x:=\frac{a}{n_x},\qquad\Delta t:=\frac{T}{n_t}

und erhalten so Gitterpunkte (xi,tj)(x_i,t_j) mit

xi:=iΔx,tj:=jΔtx_i:=i\,\Delta x,\qquad t_j:=j\,\Delta t

für i=0,1,,nxi=0,1,\ldots,n_x und j=0,1,,ntj=0,1,\ldots,n_t.

Gesucht sind die Werte

ui,j:=u(xi,tj)u_{i,j}:=u(x_i,t_j)

von uu an den Gitterpunkten.

Die Gitterwerte der Funktion ff in der Anfangsbedingung bezeichnen wir entsprechend mit

fi:=f(xi).f_i:=f(x_i).

8.2.2Innere Punkte

Für Punkte im Inneren des betrachteten Gebietes soll die Wellengleichung gelten, wobei wir die zweiten Ableitungen durch entsprechende Differenzenquotienten ersetzen:

c2ui1,j2ui,j+ui+1,j(Δx)2ui,j12ui,j+ui,j+1(Δt)2=0c^2\,\frac{u_{i-1,j}-2\,u_{i,j}+u_{i+1,j}}{(\Delta x)^2}-\frac{u_{i,j-1}-2\,u_{i,j}+u_{i,j+1}}{(\Delta t)^2}=0

für i=1,,nx1i=1,\ldots,n_x-1 und j=1,,nt1j=1,\ldots,n_t-1.

Umstellen nach ui,j+1u_{i,j+1} liefert

ui,j+1=(cΔtΔx)2(ui1,j2ui,j+ui+1,j)(ui,j12ui,j).u_{i,j+1}=\left(\frac{c\,\Delta t}{\Delta x}\right)^2\,(u_{i-1,j}-2\,u_{i,j}+u_{i+1,j})-(u_{i,j-1}-2\,u_{i,j}).

Sind für ein festes jj die ui,j1u_{i,j-1} und die ui,ju_{i,j} für alle i=0,,nxi=0,\ldots,n_x bekannt, so erhält man aus dieser Formel die ui,j+1u_{i,j+1} für i=1,,nxi=1,\ldots,n_x. Wir können uu also Zeitschritt für Zeitschritt berechnen ohne ein Gleichungssystem lösen zu müssen.

Die Obergrenze TT für die Zeit spielt hier keine Rolle. Theoretisch kann die Iteration beliebig lang laufen.

8.2.3Anfangsbedingung

Wir haben

ui,0=fiu_{i,0}=f_i

für i=0,,nxi=0,\ldots,n_x.

Die Ableitungen ut(x,0)u_t(x,0) ersetzen wir durch die zentrale Differenz

ut(xi,0)ui,1u(xi,Δt)2Δt.u_t(x_i,0)\approx\frac{u_{i,1}-u(x_i,-\Delta t)}{2\,\Delta t}.

Im Vergleich zu einem einseitigen Differenzenquotient erscheint dies sinnvoller, da der Zeitbereich [0,T][0,T] nur einen Ausschnitt aus einem größeren Zeitbereich darstellt, der beobachtete Prozess also auch schon vor t=0t=0 läuft. Andererseits bringt diese Wahl das (scheinbare) Problem mit sich, dass wir u(xi,Δt)u(x_i,-\Delta t) nicht kennen. Aus der kontinuierlichen Anfangsbedingung ut(x,0)=0u_t(x,0)=0 wird nun die diskrete Anfangsbedingung

ui,1=u(xi,Δt),i=0,,nx.u_{i,1}=u(x_i,-\Delta t),\quad i=0,\ldots,n_x.

In Formel (11) für j=0j=0 eingesetzt erhalten wir für den ersten Zeitschritt die Formel

ui,1=(cΔtΔx)2(ui1,02ui,0+ui+1,0)(ui,12ui,0).u_{i,1}=\left(\frac{c\,\Delta t}{\Delta x}\right)^2(u_{i-1,0}-2\,u_{i,0}+u_{i+1,0})-({\color{red}u_{i,1}}-2\,u_{i,0}).

Auflösen nach ui,1u_{i,1} liefert nun

ui,1=12(cΔtΔx)2(ui1,02ui,0+ui+1,0)+ui,0.u_{i,1}=\frac{1}{2}\,\left(\frac{c\,\Delta t}{\Delta x}\right)^2\,(u_{i-1,0}-2\,u_{i,0}+u_{i+1,0})+\,u_{i,0}.

sodass die unbekannten Werte u(xi,Δt)u(x_i,-\Delta t) gar keine Rolle spielen.

8.2.4Randbedingungen

8.2.4.1Dirichlet

Die diskretisierten Dirichlet-Randbedingungen sind

u0,j=unx,j=0,j=0,,nt.u_{0,j}=u_{n_x,j}=0,\qquad j=0,\ldots,n_t.

Für die Neumann-Randbedingung links verwenden wir analog zur Zeitableitung in der Anfangsbedingung die zentrale Differenz

ux(0,tj)u1,ju(Δx,tj)2Δx,u_x(0,t_j)\approx\frac{u_{1,j}-u(-\Delta x,t_j)}{2\,\Delta x},

welche zur Bedingung

u1,j=u(Δx,tj),j=0,,ntu_{1,j}=u(-\Delta x,t_j),\qquad j=0,\ldots,n_t

führt. Betrachten wir nun (11) für i=0i=0 und setzen dort diese Beziehung ein, so erhalten wir mit

u0,j+1=(cΔtΔx)2(u1,j2u0,j+u1,j)(u0,j12u0,j).u_{0,j+1}=\left(\frac{c\,\Delta t}{\Delta x}\right)^2\,({\color{red}u_{1,j}}-2\,u_{0,j}+u_{1,j})-(u_{0,j-1}-2\,u_{0,j}).

eine Formel zur Berechnung des Randwertes u0,j+1u_{0,j+1}, welche für j1j\geq 1 funktioniert. Für j=0j=0 kann mittels Einsetzen von (14) für i=0i=0 und Auflösen nach u0,1u_{0,1} daraus widerum

u0,1=12(cΔtΔx)2(u1,02u0,0+u1,0)+u0,0u_{0,1}=\frac{1}{2}\,\left(\frac{c\,\Delta t}{\Delta x}\right)^2\,(u_{1,0}-2\,u_{0,0}+u_{1,0})+\,u_{0,0}

gewonnen werden.

8.2.4.3Neumann rechts

Analog zum linken Rand erhält man für den rechten Rand die Berechnungsvorschriften

unx,j+1=(cΔtΔx)2(unx1,j2unx,j+unx1,j)(unx,j12unx,j).u_{n_x,j+1}=\left(\frac{c\,\Delta t}{\Delta x}\right)^2\,(u_{n_x-1,j}-2\,u_{n_x,j}+u_{n_x-1,j})-(u_{n_x,j-1}-2\,u_{n_x,j}).

für j1j\geq 1 und

unx,1=12(cΔtΔx)2(unx1,02unx,0+unx1,0)+unx,0.u_{n_x,1}=\frac{1}{2}\,\left(\frac{c\,\Delta t}{\Delta x}\right)^2\,(u_{n_x-1,0}-2\,u_{n_x,0}+u_{n_x-1,0})+\,u_{n_x,0}.

8.2.4.4Freier Rand rechts

Im Prinzip können die beiden Ableitungen in der Bedingung ut(a,t)+cux(a,t)=0u_t(a,t)+c\,u_x(a,t)=0 wie oben durch zentrale Differenzenquotienten ersetzt werden. Die auftretenden Funktionswerte außerhalb der Orts- und Zeitintervalle [0,a][0,a] und [0,T][0,T] können dann wieder eliminiert werden, was allerdings recht aufwendig ist. Der Einfacheit halber beschränken wir uns hier auf einseitige Differenzenquotienten (vorwärts in der Zeit, rückwärts im Ort):

ut(a,tj)+cux(a,tj)unx,j+1unx,jΔt+cunx,junx1,jΔx.u_t(a,t_j)+c\,u_x(a,t_j)\approx\frac{u_{n_x,j+1}-u_{n_x,j}}{\Delta t}+c\,\frac{u_{n_x,j}-u_{n_x-1,j}}{\Delta x}.

Nullsetzen und Umstellen nach unx,j+1u_{n_x,j+1} liefert

unx,j+1=unx,jcΔtΔx(unx,junx1,j)u_{n_x,j+1}=u_{n_x,j}-\frac{c\,\Delta t}{\Delta x}\,(u_{n_x,j}-u_{n_x-1,j})

für j0j\geq 0.

8.2.4.5Zusammenfassung

Zur Berechnung aller ui,ju_{i,j} bietet sich nun folgendes Vorgehen an:

  1. Für j=0j=0 die Werte fif_{i} verwenden.

  2. Für j=1j=1 und i=1,,nx1i=1,\ldots,n_x-1 die aus der Anfangsbedingung abgeleitete Formel für innere Punkte verwenden.

  3. Für j=1j=1 und i{0,nx}i\in\{0,n_x\} die Formeln aus den Randbedingungen nutzen.

  4. Für j=2,3,j=2,3,\ldots:

    1. Für i=1,,nx1i=1,\ldots,n_x-1 die Formel für innere Punkte nutzen.

    2. Für i{0,nx}i\in\{0,n_x\} die Formeln aus den Randbedingungen nutzen.

8.3Kondition

Man kann zeigen, dass die Berechnung der Funktion uu auf dem Gitter gut konditioniert ist, wenn

cΔtΔx1\frac{c\,\Delta t}{\Delta x}\leq 1

gilt. Der Wert auf der linken Seite heißt auch Courant-Zahl.

8.4Konvergenz

Man kann zeigen, dass für

cΔtΔx=1\frac{c\,\Delta t}{\Delta x}=1

die auf dem Gitter (ohne Rundungsfehler) berechneten Werte für uu exakt sind. Werden Δx\Delta x und Δt\Delta t gleichmäßig verkleinert, so konvergiert die Lösung des diskretisierten Problems also gegen die exakte Lösung.

Ist die Courant-Zahl kleiner als 1, so treten im Laufe der Zeit immer größere Abweichungen zwischen diskretisierter und exakter Lösung auf.

8.5Implementierung

Die Implementierung kann entsprechend den Formeln oben erfolgen.

import numpy as np
import matplotlib.pyplot as plt

def wave1d(a, T, c, f, nx, nt, left='d', right='d'):

    delta_x = a / nx
    delta_t = T / nt
    C = c * delta_t / delta_x
    print(f'Courant-Zahl: {C:.2f}')
    
    x = np.linspace(0, a, nx + 1)
    u = np.empty((nt + 1, nx + 1), dtype=float)

    # Anfangsbedingungen
    u[0, :] = f(x)
    u[1, 1:-1] = 0.5 * C ** 2 * (u[0, :-2] - 2 * u[0, 1:-1] + u[0, 2:]) + u[0, 1:-1]
    if left == 'd':
        u[1, 0] = 0
    else:
        u[1, 0] = 0.5 * C ** 2 * (2 * u[0, 1] - 2 * u[0, 0]) + u[0, 0]
    if right == 'd':
        u[1, -1] = 0
    elif right == 'n':
        u[1, -1] = 0.5 * C ** 2 * (2 * u[0, -2] - 2 * u[0, -1]) + u[0, -1]
    else:
        u[1, -1] = u[0, -1] - C * (u[0, -1] - u[0, -2])

    # Zeitschritte
    for j in range(1, nt):

        # innere Punkte
        u[j + 1, 1:-1] = C ** 2 * (u[j, :-2] - 2 * u[j, 1:-1] + u[j, 2:]) \
                         - (u[j - 1, 1:-1] - 2 * u[j, 1:-1])

        # Randbedingungen
        if left == 'd':
            u[j + 1, 0] = 0
        else:
            u[j + 1, 0] = 0.5 * C ** 2 * (2 * u[j, 1] - 2 * u[j, 0]) \
                          - (u[j - 1, 0] - 2 * u[j, 0])
        if right == 'd':
            u[j + 1, -1] = 0
        elif right == 'n':
            u[j + 1, -1] = 0.5 * C ** 2 * (2 * u[j, -2] - 2 * u[j, -1]) \
                           - (u[j - 1, -1] - 2 * u[j, -1])
        else:
            u[j + 1, -1] = u[j, -1] - C * (u[j, -1] - u[j, -2])
    
    return u

Die einzelnen Zeitschritt können als Animation visualisiert werden.

import matplotlib.animation as anim
from IPython.display import HTML

def show_anim(u, a, T):

    nt = u.shape[0] - 1
    nx = u.shape[1] - 1
    x = np.linspace(0, a, nx + 1)

    # Bilsrate auf 25 fps begrenzen
    fps_real = nt / T
    fps_show = 25
    skip_frames = int(np.floor(fps_real / fps_show))
    n_frames = (nt + 1) // (skip_frames + 1)
    
    # Figure/Axes erstellen
    fig, ax = plt.subplots()
    ax.set_xlim(0, a)
    ax.set_ylim(-1.1, 1.1)
    ax.set_xlabel('x')
    ax.set_ylabel('u(x,t)')
    ax.grid()
    
    # Linienobjekt erstellen (wird während Animation verändert)
    line = ax.plot(0, 0, '-b')[0]
    
    # Update-Funktion
    def update(frame):
        line.set_data(x, u[(skip_frames + 1) * frame, :])
    
    # Animation erstellen und anzeigen
    fa = anim.FuncAnimation(fig, update, frames=n_frames, interval=1000/fps_show)
    display(HTML(fa.to_jshtml()))
    plt.close()  # verhindert automatischen Aufruf von plt.show() durch Jupyter

8.6Experimente

Verwenden folgende Parameter:

a = 2
T = 4
c = 1
nx = 100
nt = int(np.ceil(c * nx * T / a))  # Courant = 1 oder wenigstens <= 1
print(f'{nt} Zeitschritte')
200 Zeitschritte

8.6.1Dirichlet-Randbedingungen

f = lambda x: np.sin(1/a * np.pi * x)
u = wave1d(a, T, c, f, nx, nt, 'd', 'd')
show_anim(u, a, T)
Courant-Zahl: 1.00
Loading...

8.6.2Neumann-Randbedingungen

Möchten wir Neumann-Randbedingungen verwenden, so sollte die Anfangsbedingung diese auch erfüllen:

f = lambda x: np.cos(2/a * np.pi * x)
u = wave1d(a, T, c, f, nx, nt, 'n', 'n')
show_anim(u, a, T)
Courant-Zahl: 1.00
Loading...
f = lambda x: np.exp(-(x - 0.5 * a) ** 2 / 0.05)
u = wave1d(a, T, c, f, nx, nt, 'n', 'f')
show_anim(u, a, T)
Courant-Zahl: 1.00
Loading...

Verlängern den Beobachtungszeitraum um eine volle Periode des Schwingungsvorgangs zu sehen:

T = 8
nt = int(np.ceil(c * nx * T / a))  # Courant = 1 oder wenigstens <= 1
print(f'{nt} Zeitschritte')

u = wave1d(a, T, c, f, nx, nt, 'd', 'n')
show_anim(u, a, T)
400 Zeitschritte
Courant-Zahl: 1.00
Loading...

8.6.5Courant-Zahl zu groß

T = 4
nt = int(np.ceil(0.99 * c * nx * T / a))  # Courant > 1
print(f'{nt} Zeitschritte')

f = lambda x: np.sin(1/a * np.pi * x)
u = wave1d(a, T, c, f, nx, nt, 'd', 'd')
show_anim(u, a, T)
198 Zeitschritte
Courant-Zahl: 1.01
Loading...

Obwohl die Courant-Zahl hier nur ganz wenig über der Eins liegt, wird die Berechnung schlecht konditioniert, sodass die unmeidbaren Rundungsfehler innerhalb weniger Rechenschritte extrem verstärkt werden und die numerische Lösung nichts mehr mit der exakten Lösungen des diskretisierten Problems zu tun hat.