Search code examples
pythonnumpynumerical-methodsdifferential-equationspde

Use numpy to solve transport equation with wave-like initial condition


I'm trying to write a python program to solve the first order 1-D wave equation (transport equation) using the explicit Euler method with 2nd order spatial discretization and periodic boundary conditions.

I'm new to python and I wrote this program using numpy but I think I'm making a mistake somewhere because the wave gets distorted. Instead of the wave simply translating to the left it seems to get distorted once it leaves the left boundary. I'm pretty sure this is a programming error but is it possible it's a rounding error? Am I not using numpy correctly? Any advice on writing this program in a more python-esque way? Thanks!

The PDE is waveeq

in finite difference form it is fdeq

solving for ujn1

sol

Here is what I attempted:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

# wave speed
c = 1

# spatial domain
xmin = 0
xmax = 1

n = 50 # num of grid points

# x grid of n points
X, dx = np.linspace(xmin,xmax,n,retstep=True)

# for CFL of 0.1
dt = 0.1*dx/c

# initial conditions
def initial_u(x):
    return np.exp(-0.5*np.power(((x-0.5)/0.08), 2))

# each value of the U array contains the solution for all x values at each timestep
U = []

# explicit euler solution
def u(x, t):
    if t == 0: # initial condition
        return initial_u(x)
    uvals = [] # u values for this time step
    for j in range(len(x)):
        if j == 0: # left boundary
            uvals.append(U[t-1][j] + c*dt/(2*dx)*(U[t-1][j+1]-U[t-1][n-1]))
        elif j == n-1: # right boundary
            uvals.append(U[t-1][j] + c*dt/(2*dx)*(U[t-1][0]-U[t-1][j-1]))
        else:
            uvals.append(U[t-1][j] + c*dt/(2*dx)*(U[t-1][j+1]-U[t-1][j-1]))
    return uvals

# solve for 500 time steps
for t in range(500):
    U.append(u(X, t))

# plot solution
plt.style.use('dark_background')
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)

# animate the time data
k = 0
def animate(i):
    global k
    x = U[k]
    k += 1
    ax1.clear()
    plt.plot(X,x,color='cyan')
    plt.grid(True)
    plt.ylim([-2,2])
    plt.xlim([0,1])

anim = animation.FuncAnimation(fig,animate,frames=360,interval=20)
plt.show()

this is how the wave starts enter image description here

and this is how it ends up after a few iterations enter image description here

Can anyone please explain why this (the wave distortion) is happening?


Solution

  • Your implementation is correct. The distortion comes from relatively large spatial step dx. At its current value of 0.2 it is comparable to the size of the wave, which makes the wave visibly polygonal on the graph. These these discretization errors accumulate over 500 steps. This is what I get from plt.plot(X, U[-1]) with your code:

    distorted

    And this is what I get after using n = 100 (halving both time and space stepsize), running the solution for t in range(1000) to compensate for smaller time step, and again plotting plt.plot(X, U[-1]):

    much better

    The symmetric-difference approximation for du/dx has error of order dx**3 proportional to the third derivative. The manner in which these accumulate is complicated because the solution is moving around, but in any case smaller dx improves the matters if dt scales with it.