Numerical methods
How are computers used to solve partial differential equations? On this page the diffusion equation is solved numerically using two approaches. First the equation is solved analytically, since the known solution can be derived in this way.. This analytical solution is used to compare several numerical schemes.
import numpy as np
import matplotlib.pyplot as plt
import scipy
Set the input values:
Length, $L=5$ m
Duration, 10 s
Diffusion coefficent, $\alpha=0.01$
L = 5
duration = 10
alpha = 0.05
Set the boundary conditions and inital conditions,
left_BC = 0
right_BC = 0
x_in = np.linspace(0,L,500)
eta_in = np.zeros_like(x_in)
eta_in[100:400] = 0.1*np.sin(np.pi*np.linspace(0,np.pi*0.25,300))
plt.figure()
plt.plot(x_in, eta_in)
plt.xlabel('$x$ [$m$]')
plt.ylabel('$\eta$ [$-$]')
Text(0, 0.5, '$\\eta$ [$-$]')
The exact solution is needed to evaluate the numerical methods. The following function (sol()) contains the exact solution of the diffusion equation.
def sol(duration,Nx,Nt,fx,alpha,L,Nsol=500,eta_in=None):
"""
Compute solution of 1D diffusion equation
"""
# set dimensions
x = np.linspace(0,L,Nx)
x = x[np.newaxis,:]
t = np.linspace(0,duration,Nt)
t = t[:,np.newaxis]
## set initial condition
if eta_in is None:
eta = np.zeros((len(t),Nx))
for ii in range(Nsol):
n = ii+1
Dn = 2 /L * np.trapz(fx*np.sin(n*np.pi*x/L),x)
eta = eta + Dn * np.sin(n*np.pi*x/L) * np.exp(-n**2*np.pi**2*alpha*t/L**2)
return eta
eta_sol = sol(duration,500,200,eta_in,alpha,L,Nsol=100)
x_sol = np.linspace(0,L,500)
t_sol = np.linspace(0,L,200)
plt.figure()
plt.pcolor(x_sol,t_sol,eta_sol,cmap='plasma')
plt.colorbar()
plt.xlabel('$x$ [$m$]')
plt.ylabel('$t$ [$s$]')
Text(0, 0.5, '$t$ [$s$]')
The following functions (explicit en implicit) contain the implicit and explicit numerical methods,
def explicit(Nx,Nt,eta0, dt,dx):
eta_exp = np.zeros((Nt,Nx))
F = alpha * dt/dx**2
eta_exp[:,0] = left_BC
eta_exp[:,-1] = right_BC
eta_exp[0,:] = eta0
for jj in range(Nt-1):
for ii in range(Nx):
if ii==0 or ii==Nx-1:
continue
##
eta_exp[jj+1,ii] = eta_exp[jj,ii] + F * (eta_exp[jj,ii+1]-2*eta_exp[jj,ii]+eta_exp[jj,ii-1])
return eta_exp
def implicit(Nx,Nt, dt,dx,eta0,left_BC,right_BC):
eta_implicit = np.zeros((Nt,Nx))
eta_implicit[0,:] = eta0
A = np.zeros((Nx, Nx))
b = np.zeros(Nx)
F = alpha * dt/dx**2
## matrix A
for i in range(Nx-1):
A[i,i-1] = -F
A[i,i+1] = -F
A[i,i] = 1 + 2*F
A[0,0] = A[Nx-1,Nx-1] = 1
for jj in range(Nt):
if jj==0:
continue
b = eta_implicit[jj-1,:]
## bc
b[0] =left_BC
b[Nx-1] = right_BC
eta_implicit[jj,:] = scipy.linalg.solve(A, b)
return eta_implicit
Now let's compute the solution with both methods
## grid
Nx = 100
Nt = 1000
x = np.linspace(0,L,Nx)
t = np.linspace(0,duration,Nt)
eta_exp = np.zeros((len(t),len(x)))
eta_imp = np.zeros((len(t),len(x)))
## resolution
dx = np.mean(np.diff(x))
dt = np.mean(np.diff(t))
## initial condition
eta0 = np.interp(x,x_in,eta_in)
## compute explicit
eta_exp = explicit(Nx,Nt,eta0, dt, dx)
## compute implicit
eta_im = implicit(Nx,Nt, dt,dx,eta0,left_BC,right_BC)
plt.figure()
ax = plt.subplot(2,1,1)
plt.plot(x,eta_exp[0,:],'k--')
plt.plot(x,eta_exp[-1,:],'b-',linewidth=3)
plt.plot(x,eta_im[-1,:],'r-')
plt.plot(x_sol,eta_sol[-1,:],'k:')
plt.legend(['$\eta(t=0)$','Explicit','Implicit','Analytical'])
plt.title('t={:2.2f}s'.format(t[-1]))
plt.ylabel('$\eta$')
plt.xlabel('$x$ [$m$]')
plt.subplot(2,1,2,sharex=ax)
eta_exp_int = np.interp(x_sol,x,eta_exp[-1,:])
eta_im_int = np.interp(x_sol,x,eta_im[-1,:])
plt.plot(x_sol,eta_exp_int-eta_sol[-1,:],'b',linewidth=3)
plt.plot(x_sol,eta_im_int-eta_sol[-1,:],'r')
plt.ylabel('$\Delta \eta$')
plt.xlabel('$x$ [$m$]')
Text(0.5, 0, '$x$')
The solution for both methods is stable when $F$ is smaller than 0.5. So let's compute F,
F = alpha * dt/dx**2
print(F)
0.19621621621621615
When $F$ is larger than one, the explicit method wont work anymore.
## grid
Nx = 500
Nt = 1000
x = np.linspace(0,L,Nx)
t = np.linspace(0,duration,Nt)
eta_exp = np.zeros((len(t),len(x)))
eta_imp = np.zeros((len(t),len(x)))
## resolution
dx = np.mean(np.diff(x))
dt = np.mean(np.diff(t))
## initial condition
eta0 = np.interp(x,x_in,eta_in)
## compute explicit
eta_exp = explicit(Nx,Nt,eta0, dt, dx)
## compute implicit
eta_im = implicit(Nx,Nt, dt,dx,eta0,left_BC,right_BC)
time_index = 1
plt.figure()
ax = plt.subplot(2,1,1)
plt.plot(x,eta_exp[0,:],'k--')
plt.plot(x,eta_exp[time_index,:],'b-',linewidth=3)
plt.plot(x,eta_im[time_index,:],'r-')
plt.plot(x_sol,eta_sol[time_index,:],'k:')
plt.legend(['$\eta(t=0)$','Explicit','Implicit','Analytical'])
plt.title('t={:2.2f}s'.format(t[time_index]))
plt.ylabel('$\eta$')
plt.xlabel('$x$ [$m$]')
plt.subplot(2,1,2,sharex=ax)
eta_exp_int = np.interp(x_sol,x,eta_exp[time_index,:])
eta_im_int = np.interp(x_sol,x,eta_im[time_index,:])
plt.plot(x_sol,eta_exp_int-eta_sol[time_index,:],'b',linewidth=3)
plt.plot(x_sol,eta_im_int-eta_sol[time_index,:],'r')
plt.ylabel('$\Delta \eta$')
plt.xlabel('$x$ [$m$]')
Text(0.5, 0, '$x$')
In the following section, the error is computed for various values of $F$.
Nx_list = np.array([25, 75,100, 200,500])
error_exp =np.zeros((len(Nx_list)))
error_imp =np.zeros((len(Nx_list)))
F_list =np.zeros((len(Nx_list)))
time_index = -1
for kk, Nx in enumerate(Nx_list):
Nt = 1000
x = np.linspace(0,L,Nx)
t = np.linspace(0,duration,Nt)
eta_exp = np.zeros((len(t),len(x)))
eta_imp = np.zeros((len(t),len(x)))
## resolution
dx = np.mean(np.diff(x))
dt = np.mean(np.diff(t))
## initial condition
eta0 = np.interp(x,x_in,eta_in)
## compute explicit
eta_exp = explicit(Nx,Nt,eta0, dt, dx)
## compute implicit
eta_im = implicit(Nx,Nt, dt,dx,eta0,left_BC,right_BC)
eta_exp_int = np.interp(x_sol,x,eta_exp[-1,:])
eta_im_int = np.interp(x_sol,x,eta_im[-1,:])
error_exp[kk] = np.abs(eta_exp_int[400]-eta_sol[-1,400])
error_imp[kk] = np.abs(eta_im_int[400]-eta_sol[-1,400])
F_list[kk] = alpha * dt/dx**2
if kk==0:
plt.figure()
ax = plt.subplot(2,1,1)
plt.plot(x_sol,eta_sol[time_index,:],'k:')
plt.title('t={:2.2f}s'.format(t[time_index]))
plt.ylabel('$\eta$')
plt.xlabel('$x$ [$m$]')
ax2 = plt.subplot(2,1,2)
ax2.plot(x_sol,eta_sol[time_index,:]*0,'k:')
plt.ylabel('$\Delta\eta$')
plt.xlabel('$x$ [$m$]')
ax.plot(x,eta_im[time_index,:])
eta_im_int = np.interp(x_sol,x,eta_im[time_index,:])
ax2.plot(x_sol,eta_im_int-eta_sol[time_index,:])
labels = ['Solution'] + Nx_list.tolist()
plt.legend(labels)
plt.figure()
plt.plot(F_list,error_imp,'-bo')
plt.plot(F_list,error_exp,'-ro')
plt.ylim(0, np.max(error_imp))
plt.xlabel('$F$')
plt.ylabel('$\Delta \eta$')
Text(0, 0.5, '$\\Delta \\eta$')