#!/usr/bin/env python
# coding: utf-8
# In[304]:
from IPython.display import HTML
s="""
Lax-Friedrichs, Lax-Wendroff, MacCormack and Beam-Warming Schemes
""";
h=HTML(s); h
# # Understand the Problem
#
# ## Question
#
# * What is the 1D velocity for the **1D Burgers Equation** for flow with a step function for the initial conditions?
# * Invesgiate the propagation with the:
#
# 1. LF
# 2. LW
# 3. MacCormack
#
#
# * $\Delta x$ = $\Delta t$ = 0.1
# * Change ${{\Delta t} \over {\Delta x}} $from 1 to 0.5 (effect of Courant Number)
# * Change to a finer mesh (effect of step sizes)
#
# ## Initial Conditions
#
# * `t = 0s` and `x = 0m to 2m` $\Rightarrow$ ` u = 1m/s`
#
# * `t = 0s` and `x = 2m to 4m` $\Rightarrow$ `u = 0m/s`
#
# ## Boundary Conditions
#
# * `x = 0m` $\Rightarrow$ ` u = 1m/s`
#
# * `y = 2m` $\Rightarrow$ ` u = 0m/s`
#
# ## Governing Equation
#
# * The Burgers Equation is a wave that propagates with different speeds at different points in the solution
# * Eventually a shock will be formed
#
# * In **Non-conservative Form** the Burgers Equation is described as follows:
#
# $$ {\partial u \over \partial t} + u {\partial u \over \partial x} = 0 $$
#
# Non-conservative form will not be good at resolving problems with **sharp changes**.
#
# * In **Conservative Form** the Burgers Equation is described as follows (for the derivation see http://www.thevisualroom.com/conservative_form.html)
#
# $$ {\partial u \over \partial t} + {\partial \over {\partial x}}{ \left( {u^2 \over 2} \right)} = 0 $$
#
# Alternatively, one can write this:
#
# $$ {\partial u \over \partial t} + {\partial F \over {\partial x}} = 0 $$
#
# where flux:
#
# $$ F = {{u^2} \over 2} $$
#
# ***
# # Formulate the Problem
#
# ## Input Data:
#
# Burgers Equation **does have a temporal component**, so we use `nt`
#
# * `nt` = ? (number of temporal points)
# * `nx` = 51 (number of x spatial points)
# * `tmax` = 1.0
# * `xmax` = 4.0
#
# Formulate `nt` based on the CFL Condition `sigma = (dt / dx) = 1`
#
# ## Initial Conditions:
#
# * $\forall (x, t) \quad t = 0 \land x \le 2 \rightarrow u(x,0) = 2 $
# * $\forall (x, t) \quad t = 0 \land x \gt 2 \rightarrow u(x,0) = 0 $
#
# ## Velocity Boundary Conditions:
#
# * $\forall (x, t) \quad x = 0 \rightarrow u(0,t) = 2 $
# * $\forall (x, t) \quad x = 4 \rightarrow u(4,t) = 0 $
#
# ## Output Data:
#
# * $\forall (x, t) \quad \ u(x,t) = ? $
#
# ***
#
#
# # Design Algorithm to Solve Problem using Lax-Friedrichs Scheme
#
# ## Space-time discretisation:
#
# * i $\rightarrow$ index of grid in x
# * n $\rightarrow$ index of time
#
# ## Lax-Friedrichs Numerical scheme
#
# * For the **one** first derivative of **velocity** in time: 1st order FD in time
# * For the **one** first derivative of **flux** in space: 2nd order CD in space
# * Replace $u_i^n = {1 \over 2} (u_{i+1}^n+u_{i-1}^n)$
#
# ## Discrete equation
#
# $$ {{u_i^{n+1} - {1 \over 2}(u_{i+1}^n+u_{i-1}^n)} \over {\Delta t}} + {{F_{i+1}^n - F_{i-1}^n} \over {2 \Delta x}}=0 $$
#
# ## Transpose
#
# $$ u_i^{n+1} = {1 \over 2} (u_{i-1}^n + u_{i+1}^n) - {\sigma \over 2} (F_{i+1}^n - F_{i-1}^n) $$
#
# ## Pseudo-code
# #Constants
# xmax = 4.0
# nx = 51
# dx = xmax/(nx-1)
# tmax = 1.0
# sigma = 1.0
# dt = dx * sigma
# nt = int(tmax/dt + 1)
#
# #Boundary Conditions
# for n between 0 and nt-1
# u(0,n)=1
# u(nx-1,n)=0
#
# #Initial Conditions
# for i between 1 and nx-2
# if(i < (nx-1)/2)
# u(i,0) = 1
# else
# u(i,0) = 0
#
# F = (u/2)**2
#
# #Iteration
# for n between 0 and nt-1
# for i between 1 and nx-2
# u(i,n+1) = 0.5*[ u(i-1,n)+u(i+1,n) ] - 0.5*sigma*[ F(i+1,n)-F(i-1,n) ]
# In[5]:
def initial_and_boundary_conditions(nx, nt):
# Initialise data structures
import numpy as np
u = np.zeros((nx,nt))
# Boundary conditions
u[0,:] = 1.0
u[nx-1,:] = 0.0
half = int((nx-1)/2)
# Initial conditions
u[0:half,0] = 1.0
return u
# In[130]:
def initial_and_boundary_conditions_2(nx, nt):
# Initialise data structures
import numpy as np
u = np.zeros((nx,nt))
# Boundary conditions
u[0,:] = 1.1
u[nx-1,:] = 0.0
half = int((nx-1)/2)
# Initial conditions
u[0:half,0] = 1.1
return u
# In[3]:
def analytical(nx, tmax, xmax):
"""
Returns the velocity field and distance for 1D non-linear convection
This is just transporting the ICs with a Courant number of 2
(effectively a numerical wave speed of 0.5)
"""
# Increments
# dx = xmax/(nx-1)
# dt = dx*sigma
# tmax = (nt-1)*dt
dx = xmax/(nx-1)
dt = dx/0.5
nt = int((tmax/dt)+1)
# Initial and Boundary Conditions
u = initial_and_boundary_conditions(nx, nt)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
i = 1
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,int(nt-1)):
u[i:nx-1, n+1] = u[i-1:nx-2, n]
return u, x, nt
# In[1]:
def analytical_2(nx, tmax, xmax):
"""
Returns the velocity field and distance for 1D non-linear convection
This is just transporting the ICs with a Courant number of 2
(effectively a numerical wave speed of 0.5)
"""
# Increments
# dx = xmax/(nx-1)
# dt = dx*sigma
# tmax = (nt-1)*dt
dx = xmax/(nx-1)
dt = dx / (((1.1+0.0)/2.0))
nt = int(tmax/dt + 1)
# Initial and Boundary Conditions
u = initial_and_boundary_conditions_2(nx, nt)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
i = 1
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,int(nt-1)):
u[i:nx-1, n+1] = u[i-1:nx-2, n]
return u, x, nt
# In[215]:
def plot(u,x,NT,u_analytical, x_analytical, NT2, step, dt, dt_lf):
"""
Plots the 1D velocity field
"""
import matplotlib.pyplot as plt
import matplotlib.cm as cm
plt.figure()
ax=plt.subplot(111)
colour=iter(cm.rainbow(np.linspace(0,step,NT)))
for n in range(0,NT,step):
t = n*dt
c=next(colour)
ax.plot(x,u[:,n],linestyle='-',c=c,label='t='+str(t))
t_lf =(NT2-1)*dt_lf
ax.plot(x_analytical,u_analytical[:,NT2-1],linestyle='--',c='k',label='t='+str(t_lf)+' analytical')
box=ax.get_position()
ax.set_position([box.x0, box.y0, box.width*2,box.height*2])
ax.legend( bbox_to_anchor=(1.02,1), loc=2)
plt.ylim([-0.5,1.5])
plt.xlim([0.0,4.0])
plt.xlabel('x (m)')
plt.ylabel('u (m/s)')
plt.show()
# In[2]:
def lax_friedrichs_convection(sigma, nx, nt, xmax, step):
"""
Returns the velocity field and distance for 1D linear convection
"""
# Increments
# dx = xmax/(nx-1)
# dt = dx*sigma
# tmax = (nt-1)*dt
dx = xmax/(nx-1)
dt = dx * sigma
nt = int(tmax/dt + 1)
# Initial and Boundary Conditions
u = initial_and_boundary_conditions(nx, nt)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
# Lambda function for Flux:
F = lambda u: u**2.0 / 2.0
i = 1
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,int(nt-1)):
u[i:nx-1, n+1] = (0.5*(u[i-1:nx-2, n]+u[i+1:nx, n])-
0.5*sigma*(F(u[i+1:nx, n])-F(u[i-1:nx-2, n])))
# Lax-Friedrichs with Courant Number = 2
u_lf, x_lf, nt_lf = analytical(nx, tmax, xmax)
dt_lf = dx * 2.0
plot(u,x,nt,u_lf, x_lf, nt_lf, step, dt, dt_lf)
# In[103]:
def plot_cfl(x,u_1,nt_1,u_2,nt_2,u_3,nt_3,u_lf, x_lf, nt_lf, dt_1, dt_2, dt_3, dt_lf):
"""
Plots the 1D velocity field
"""
import matplotlib.pyplot as plt
import matplotlib.cm as cm
plt.figure()
ax=plt.subplot(111)
c_1 = dt_1/(max(x)/100)
c_2 = dt_2/(max(x)/100)
c_3 = dt_3/(max(x)/100)
t_1 =(nt_1-1)*dt_1
ax.plot(x,u_1[:,nt_1-1],linestyle='-',c='r',label='t='+str(t_1)+' Courant = '+str(c_1))
t_2 =(nt_2-1)*dt_2
ax.plot(x,u_2[:,nt_2-1],linestyle='-',c='b',label='t='+str(t_2)+' Courant = ' +str(c_2))
t_3 =(nt_3-1)*dt_3
ax.plot(x,u_3[:,nt_3-1],linestyle='-',c='g',label='t='+str(t_3)+' Courant = ' +str(c_3))
t_lf =(nt_lf-1)*dt_lf
ax.plot(x_lf,u_lf[:,nt_lf-1],linestyle='--',c='k',label='t='+str(t_lf)+' Analytical')
box=ax.get_position()
ax.set_position([box.x0, box.y0, box.width*2,box.height*2])
ax.legend( bbox_to_anchor=(1.02,1), loc=2)
plt.ylim([-0.5,1.5])
plt.xlim([0.0,4.0])
plt.xlabel('x (m)')
plt.ylabel('u (m/s)')
plt.show()
# In[177]:
def lax_friedrichs_convection_cfl(sigma_1, sigma_2, sigma_3, nx, tmax, xmax):
"""
Returns the velocity field and distance for 1D linear convection
"""
# Increments
dx = xmax/(nx-1)
dt_1 = dx * sigma_1
nt_1 = int(tmax/dt_1 + 1)
dt_2 = dx * sigma_2
nt_2 = int(tmax/dt_2 + 1)
dt_3 = dx * sigma_3
nt_3 = int(tmax/dt_3 + 1)
# Initial and Boundary Conditions
u_1 = initial_and_boundary_conditions(nx, nt_1)
u_2 = initial_and_boundary_conditions(nx, nt_2)
u_3 = initial_and_boundary_conditions(nx, nt_3)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
# Lambda function for Flux:
F = lambda u: u**2.0 / 2.0
i = 1
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,nt_1-1):
u_1[i:nx-1, n+1] = (0.5*(u_1[i-1:nx-2, n]+u_1[i+1:nx, n])-
0.5*sigma_1*(F(u_1[i+1:nx, n])-F(u_1[i-1:nx-2, n])))
for n in range(0,nt_2-1):
u_2[i:nx-1, n+1] = (0.5*(u_2[i-1:nx-2, n]+u_2[i+1:nx, n])-
0.5*sigma_2*(F(u_2[i+1:nx, n])-F(u_2[i-1:nx-2, n])))
for n in range(0,nt_3-1):
u_3[i:nx-1, n+1] = (0.5*(u_3[i-1:nx-2, n]+u_3[i+1:nx, n])-
0.5*sigma_3*(F(u_3[i+1:nx, n])-F(u_3[i-1:nx-2, n])))
# Lax-Friedrichs with Courant Number = 2
u_lf, x_lf, nt_lf = analytical(nx, tmax, xmax)
dt_lf = dx * 2.0
plot_cfl(x,u_1,nt_1,u_2,nt_2,u_3,nt_3,u_lf, x_lf, nt_lf, dt_1, dt_2, dt_3, dt_lf)
# In[178]:
def lax_friedrichs_convection_initial(sigma_1, sigma_2, sigma_3, nx, tmax, xmax):
"""
Returns the velocity field and distance for 1D linear convection
"""
# Increments
dx = xmax/(nx-1)
dt_1 = dx * sigma_1
nt_1 = int(tmax/dt_1 + 1)
dt_2 = dx * sigma_2
nt_2 = int(tmax/dt_2 + 1)
dt_3 = dx * sigma_3
nt_3 = int(tmax/dt_3 + 1)
# Initial and Boundary Conditions
u_1 = initial_and_boundary_conditions_2(nx, nt_1)
u_2 = initial_and_boundary_conditions_2(nx, nt_2)
u_3 = initial_and_boundary_conditions_2(nx, nt_3)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
# Lambda function for Flux:
F = lambda u: u**2.0 / 2.0
i = 1
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,nt_1-1):
u_1[i:nx-1, n+1] = (0.5*(u_1[i-1:nx-2, n]+u_1[i+1:nx, n])-
0.5*sigma_1*(F(u_1[i+1:nx, n])-F(u_1[i-1:nx-2, n])))
for n in range(0,nt_2-1):
u_2[i:nx-1, n+1] = (0.5*(u_2[i-1:nx-2, n]+u_2[i+1:nx, n])-
0.5*sigma_2*(F(u_2[i+1:nx, n])-F(u_2[i-1:nx-2, n])))
for n in range(0,nt_3-1):
u_3[i:nx-1, n+1] = (0.5*(u_3[i-1:nx-2, n]+u_3[i+1:nx, n])-
0.5*sigma_3*(F(u_3[i+1:nx, n])-F(u_3[i-1:nx-2, n])))
# Passing ICs with Courant Number = 2
u_lf, x_lf, nt_lf = analytical_2(nx, tmax, xmax)
dt_lf = dx * 2.0
plot_cfl(x,u_1,nt_1,u_2,nt_2,u_3,nt_3,u_lf, x_lf, nt_lf, dt_1, dt_2, dt_3, dt_lf)
# ## Lax-Friedrichs CFL Number = 0.5
#
# Stability condition:
#
# $$ \left| {\Delta t \over \Delta x} u_{max} \right| \lt 1 $$
#
# Where $ u_{max} $ is the maximum eigenvalue of the A matrix
#
# $$ \left| u \right| \le {\Delta x \over \Delta t} $$
#
# Meaning of $ {\Delta x \over \Delta t} $ is the **velocity of the computated information**, i.e. $1 \over \sigma$, so this must be greater than or equal to **the velocity of the physical wave**.
#
# The effective wave speed is $c = {{u_R + u_L} \over 2} = 0.5 $ **this is not a stability condition - due to the condition of needing to resolve a wave of length** $2 \Delta x$
#
# Lax-Friedichs also adds an artificial diffusion term $({\Delta x^2 \over \Delta t}) {\partial^2 Q \over \partial x^2}$ with an artificial viscosity of 0.5 to the RHS of the FTCS scheme, to stabilize it using standard second order central differencing for the diffusion term
#
# However, this scheme is still **first order**. The shock is subject to numerical dissipation
# In[172]:
lax_friedrichs_convection(0.5, 101, 2.0, 4.0, 5)
# ## Lax-Friedrichs CFL Number = 1, 0.5 and 0.25
#
# * CFL Number effectively reduced the timestep, increasing the number of timesteps
# * The numerical diffusion is proportional to the number of timesteps, so that increasing the **CFL number increases the numerical dissipation**
# In[180]:
lax_friedrichs_convection_cfl(1.0, 0.5, 0.25, 101, 2.0, 4.0)
# ## Lax-Friedrichs CFL Number = 1, 1.28, 2
#
# * CFL above 1, the solution should be unstable
# * However, CFL of 2 **appears** stable
#
# ### Why is CFL = 2 stable for Lax-Friedrichs?
#
# #### Explanation from Linear Convection
#
# $$ {\partial u \over \partial t} = -c {\partial u \over \partial x} $$
#
# With FTBS:
#
# $$ {{u_i^{n+1} - u_i^n} \over \Delta t} = -c\left( {{u_i^n - u_{i-1}^n} \over \Delta x} \right)$$
#
# i.e.
#
# $$ u_i^{n+1} = u_i^n -\sigma \left( {{u_i^n - u_{i-1}^n}} \right)$$
#
# With $\sigma = 1$:
#
# $$ u_i^{n+1} = u_{i-1}^n$$
#
# i.e. the new value in time equals the upstream value - just propagating the **Initial Conditions** through the domain
#
# #### Explanation from Non-Linear Convection
#
# $$ u_i^{n+1} = {1 \over 2} (u_{i+1}^n + u_{i-1}^n)-{\sigma \over 2} \left( {{(u_{i+1}^n)^2 \over 2} - {(u_{i-1}^n)^2 \over 2}} \right)$$
#
# At the shock and $\sigma = 2$
#
# $$u_{i+1}^n = 0$$
# $$u_{i-1}^n = 1$$
#
# $$ u_i^{n+1} = {1 \over 2} (0+1)-{2 \over 2} \left( 0 -{1 \over 2} \right) = 1$$
#
# i.e. the new value equals the upstream value - similar to CFL = 1 for FTBS (upwind)
#
# **It's simply a cancellation of terms**
# In[181]:
lax_friedrichs_convection_cfl(1.0, 1.28, 2.0, 101, 2.0, 4.0)
# ### CFL=2 numerical cancellation of terms for Lax-Friedrichs is not generally applicable and depends on the ICs+BCs
#
# * This shows for ICs with a slightly higher value, the Courant Number needs to be less than 1 for stability (as it should)
#
# In[186]:
lax_friedrichs_convection_initial(0.5, 1.28, 2.0, 101, 1.0, 4.0)
# ## Conclusion from Lax-Friedrichs
#
# Lax-Friedrichs shows **diffusion error** (acculumated numerical diffusion)
#
# * Reducing the CFL number reduces the timestep
# * Reducing the timestep increases the number of iterations to get to the same point in time
# * The diffusion error is proportional to (amplitude ratio) to the power of number of timesteps
# * Therefore increasing the number of timesteps increases the diffusion error
#
# Lax-Friedrichs has best accuracy with **CFL = 1**
#
# Lax Friedrichs shows **Odd-Even Decoupling**:
#
# 1) The value of the velocity at point $u_i^{n+1}$ does not depend on the value at $u_i^n$.
#
# 2) Since $u_i^n$ depends on $u_{i-1}^{n-1}$ and $u_{i+1}^{n-1}$, then $u_i^{n+1}$ does not depend on these either
#
# This is similar to the behavior of a collocated grid when pressure and velocity are decoupled.
# # Design Algorithm to Solve Problem using Lax-Wendroff Scheme
#
# ## Space-time discretisation:
#
# * i $\rightarrow$ index of grid in x
# * n $\rightarrow$ index of time
#
# ## Conservative Form
#
# $$ {\partial u \over \partial t} + {\partial \over {\partial x}}{ \left( {u^2 \over 2} \right)} = 0 \qquad
# \Leftrightarrow \qquad {\partial u \over \partial t} + {{\partial F} \over {\partial x}} = 0$$
#
# ## Non-Conservative Form
#
# $$ {\partial u \over \partial t} + u {{\partial u} \over {\partial x}} = 0 \qquad
# \Leftrightarrow \qquad {\partial u \over \partial t} + A {{\partial u} \over {\partial x}} = 0$$
#
# * F = Flux for Conservative Form of Burgers Equation
# * A = Jacobian for Non-Conservative Form of Burgers Equation
#
# $$ A = {{\partial F} \over {\partial u}} = u $$
#
# ## Lax-Wendroff Numerical scheme
#
# * Taylor Expansion to 2nd order term:
#
# $$ u_i^{n+1} = u_i^n + {\Delta t}(u_t)_i^n + {\Delta t^2 \over 2} (u_{tt})_i^n + O(\Delta t^3) $$
#
#
# * Replace $u_t$ and $u_{tt}$ by spatial derivatives:
#
# $$ u_t = - F_x $$
#
# $$ u_{tt} = -(F_x)_t = -(F_t)_x $$
#
# $$ {{\partial F} \over {\partial t}} = {{\partial F} \over {\partial u}} {{\partial u} \over {\partial t}} = A (-F_x) = -AF_x $$
#
# Hence:
#
# $$ u_{tt} = (A F_x)_x $$
#
# And:
#
# $$ u_i^{n+1} = u_i^n - {\Delta t}(F_x)_i^n + {\Delta t^2 \over 2} ((A F_x)_x)_i^n $$
#
# * For the first derivative of **Flux** in space: 2nd order CD in space between two points
# * For the first derivative of **Jacobian times Derivative of the Flux** in space: 2nd order in space between two midpoints
# * For the first derivative of midpoint **Flux** in space: 2nd order in space
# * For the value of the **Jacobian** at the midpoint: average value
#
# ## Discrete equation
#
# $$ u_i^{n+1} = u_i^n - {{\Delta t}} {(F_{i+1}^n - F_{i-1}^n) \over {2 \Delta x}} + {{\Delta t^2} \over 2} {{(AF_x)_{i+{1 \over 2}}^n - (AF_x)_{i-{1 \over 2}}^n \over {\Delta x}}} $$
#
#
# $$ {{(AF_x)_{i+{1 \over 2}}^n - (AF_x)_{i-{1 \over 2}}^n \over {\Delta x}}} =
# {1 \over {\Delta x}}(A_{i+{1 \over 2}}^n{{{F_{i+1}^n - F_i^n} \over {\Delta x} }}) -
# {1 \over {\Delta x}}(A_{i-{1 \over 2}}^n{{{F_{i}^n - F_{i-1}^n} \over {\Delta x} }}) = \
# {1 \over {\Delta x^2}} \left( A_{i+{1 \over 2}}^n{{ ({F_{i+1}^n - F_i^n}) }} -
# A_{i-{1 \over 2}}^n{{ ({F_{i}^n - F_{i-1}^n}) }} \right) = \
# {1 \over {\Delta x^2}} \left( {{A_{i+1}^n + A_i^n} \over 2} {{ ({F_{i+1}^n - F_i^n}) }} -
# {{A_{i}^n + A_{i-1}^n} \over 2}{{ ({F_{i}^n - F_{i-1}^n}) }} \right) = \
# {1 \over {2 \Delta x^2}} \left( {({A_{i+1}^n + A_i^n}) } {{ ({F_{i+1}^n - F_i^n}) }} -
# {({A_{i}^n + A_{i-1}^n})}{{ ({F_{i}^n - F_{i-1}^n}) }} \right)$$
#
# Hence:
#
# $$ u_i^{n+1} = u_i^n - {{\Delta t}} {(F_{i+1}^n + F_{i-1}^n) \over {2 \Delta x}} + {\Delta t^2 \over {4 \Delta x^2}} \left( {({A_{i+1}^n + A_i^n}) } {{ ({F_{i+1}^n - F_i^n}) }} -
# {({A_{i}^n + A_{i-1}^n})}{{ ({F_{i}^n - F_{i-1}^n}) }} \right) $$
#
# $$ u_i^{n+1} = u_i^n - {{\sigma \over 2}} {(F_{i+1}^n - F_{i-1}^n)} + {\sigma^2 \over {4}} \left( {({A_{i+1}^n + A_i^n}) } {{ ({F_{i+1}^n - F_i^n}) }} -
# {({A_{i}^n + A_{i-1}^n})}{{ ({F_{i}^n - F_{i-1}^n}) }} \right) $$
#
# ## Pseudo-code
# #Constants
# xmax = 4.0
# nx = 51
# dx = xmax/(nx-1)
# tmax = 1.0
# sigma = 1.0
# dt = dx * sigma
# nt = int(tmax/dt + 1)
#
# #Boundary Conditions
# for n between 0 and nt-1
# u(0,n)=1
# u(nx-1,n)=0
#
# #Initial Conditions
# for i between 1 and nx-2
# if(i < (nx-1)/2)
# u(i,0) = 1
# else
# u(i,0) = 0
#
# F = (u/2)**2
# A = u
#
# #Iteration
# for n between 0 and nt-1
# for i between 1 and nx-2
# u(i,n+1) = u(i,n)-0.5*sigma*[ F(i+1,n)-F(i-1,n) ] +
# 0.25*(sigma**2)*[ ( A(i+1,n)+A(i,n) )*( F(i+1,n)-F(i,n) )-
# ( A(i,n)+A(i-1,n) )*( F(i,n)-F(i-1,n) ) ]
# In[ ]:
def lax_wendroff_convection_2(sigma, nx, nt, xmax, step):
"""
Returns the velocity field and distance for 1D linear convection
"""
# Increments
# dx = xmax/(nx-1)
# dt = dx*sigma
# tmax = (nt-1)*dt
dx = xmax/(nx-1)
dt = dx * sigma
nt = int(tmax/dt + 1)
# Initial and Boundary Conditions
u = initial_and_boundary_conditions(nx, nt)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
# Lambda function for Flux:
F = lambda u: u**2.0 / 2.0
# Lamba function for Jacobian:
A = lambda u: u
i = 1
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,nt-1):
u[i:nx-1, n+1] = (u[i:nx-1,n] -
0.5*sigma*( F(u[i+1:nx, n])-F(u[i-1:nx-2, n]) ) +
0.25*(sigma**2)*( ( A(u[i+1:nx, n])+A(u[i:nx-1, n]) )*
( F(u[i+1:nx, n])-F(u[i:nx-1, n]) )-
( A(u[i:nx-1, n])+A(u[i-1:nx-2, n]) )*
( F(u[i:nx-1, n])-F(u[i-1:nx-2, n]) ) ) )
# Wave propagating with speed 0.5
u_lf, x_lf, nt_lf = analytical(nx, tmax, xmax)
dt_lf = dx * 2.0
plot(u,x,nt,u_lf, x_lf, nt_lf, step, dt, dt_lf)
# In[194]:
def lax_wendroff_convection_cfl(sigma_1, sigma_2, sigma_3, nx, tmax, xmax):
"""
Returns the velocity field and distance for 1D linear convection
"""
# Increments
dx = xmax/(nx-1)
dt_1 = dx * sigma_1
nt_1 = int(tmax/dt_1 + 1)
dt_2 = dx * sigma_2
nt_2 = int(tmax/dt_2 + 1)
dt_3 = dx * sigma_3
nt_3 = int(tmax/dt_3 + 1)
# Initial and Boundary Conditions
u_1 = initial_and_boundary_conditions(nx, nt_1)
u_2 = initial_and_boundary_conditions(nx, nt_2)
u_3 = initial_and_boundary_conditions(nx, nt_3)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
# Lambda function for Flux:
F = lambda u: u**2.0 / 2.0
# Lamba function for Jacobian:
A = lambda u: u
i = 1
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,nt_1-1):
u_1[i:nx-1, n+1] = (u_1[i:nx-1,n] -
0.5*sigma_1*( F(u_1[i+1:nx, n])-F(u_1[i-1:nx-2, n]) ) +
0.25*(sigma_1**2)*( ( A(u_1[i+1:nx, n])+A(u_1[i:nx-1, n]) )*
( F(u_1[i+1:nx, n])-F(u_1[i:nx-1, n]) )-
( A(u_1[i:nx-1, n])+A(u_1[i-1:nx-2, n]) )*
( F(u_1[i:nx-1, n])-F(u_1[i-1:nx-2, n]) ) ) )
for n in range(0,nt_2-1):
u_2[i:nx-1, n+1] = (u_2[i:nx-1,n] -
0.5*sigma_2*( F(u_2[i+1:nx, n])-F(u_2[i-1:nx-2, n]) ) +
0.25*(sigma_2**2)*( ( A(u_2[i+1:nx, n])+A(u_2[i:nx-1, n]) )*
( F(u_2[i+1:nx, n])-F(u_2[i:nx-1, n]) )-
( A(u_2[i:nx-1, n])+A(u_2[i-1:nx-2, n]) )*
( F(u_2[i:nx-1, n])-F(u_2[i-1:nx-2, n]) ) ) )
for n in range(0,nt_3-1):
u_3[i:nx-1, n+1] = (u_3[i:nx-1,n] -
0.5*sigma_3*( F(u_3[i+1:nx, n])-F(u_3[i-1:nx-2, n]) ) +
0.25*(sigma_3**2)*( ( A(u_3[i+1:nx, n])+A(u_3[i:nx-1, n]) )*
( F(u_3[i+1:nx, n])-F(u_3[i:nx-1, n]) )-
( A(u_3[i:nx-1, n])+A(u_3[i-1:nx-2, n]) )*
( F(u_3[i:nx-1, n])-F(u_3[i-1:nx-2, n]) ) ) )
# Wave at speed 0.5
u_lf, x_lf, nt_lf = analytical(nx, tmax, xmax)
dt_lf = dx * 2.0
plot_cfl(x,u_1,nt_1,u_2,nt_2,u_3,nt_3,u_lf, x_lf, nt_lf, dt_1, dt_2, dt_3, dt_lf)
# ## Lax-Wendroff CFL = 0.25, 0.5, 1.0
# In[195]:
lax_wendroff_convection_cfl(0.25, 0.5, 1.0, 101, 2.0, 4.0)
# ## Conclusion from Lax-Wendroff
#
# Stability condition:
#
# $$ \left| {\Delta t \over \Delta x} u_{max} \right| \lt 1 $$
#
# Where $ u_{max} $ is the maximum eigenvalue of the A matrix
#
# Lax-Wendroff shows **dispersive errors** $\epsilon_{\phi}$ and very little **diffusion errors** $ \epsilon_D$ and this is because LW is second order:
#
# * When the leading error term is even ordered (as for first order methods), there will be mainly dissipation error
# * When the leading error term is odd ordered (as for second order methods), there will be mainly dispersion error
#
# Dispersion error depends on:
#
# * The dispersive errors depend on the wave content of the solution
# * Numerical velocity here is lagging
# * Oscillations are larger for smaller CFL number - this is the accumulation of numerical dispersion errors
#
#
#
# # Design Algorithm to Solve Problem using MacCormack Scheme
#
# Lax-Wendroff is ok, but it was quite complex to code, **Because of the need to compute second order derivatives**. MacCormack should be easier
#
# ## Space-time discretisation:
#
# * i $\rightarrow$ index of grid in x
# * n $\rightarrow$ index of time
#
# ## Conservative Form
#
# $$ {\partial u \over \partial t} + {\partial \over {\partial x}}{ \left( {u^2 \over 2} \right)} = 0 \qquad
# \Leftrightarrow \qquad {\partial u \over \partial t} + {{\partial F} \over {\partial x}} = 0$$
#
# ## MacCormack Numerical scheme
#
# * Define predicted terms as $ \tilde{u} $ and $ F(\tilde{u}) = \tilde{F} $
#
# * Predictor step **FTFS** from $n$ to $n+1$
#
# $$ {{ \tilde{u}_i^{n+1} - u_i^n } \over {\Delta t}} = -\left( {{F_{i+1}^n - F_i^n} \over {\Delta x}} \right) $$
#
# $$ \tilde{u}_i^{n+1} = u_i^n - \Delta t \left( {{F_{i+1}^n - F_{i}^n} \over {\Delta x}} \right) $$
#
# $$ \tilde{u}_i^{n+1} = u_i^n - \sigma \left( {{F_{i+1}^n - F_{i}^n}} \right) $$
#
# * Corrector step **FTBS** from $n+{1 \over 2}$ to $n+1$ using predicted values for time $n+1$ and where the midpoint time is the average of the predicted and current values $ u_i^{n+{1 \over 2}} = {1 \over 2} \left( \tilde{u}_i^{n+1} + u_i^n \right) $
#
# $$ {{ u_i^{n+1} - {1 \over 2} \left( \tilde{u}_i^{n+1} + u_i^n \right)} \over {{\Delta t \over 2}}} = -\left( {{\tilde{F}_{i}^{n+1} - \tilde{F}_{i-1}^{n+1}} \over {\Delta x}} \right)$$
#
# $$ u_i^{n+1} = {1 \over 2} \left( \tilde{u}_i^{n+1} + u_i^n \right) - {\Delta t \over 2} \left( {{\tilde{F}_{i}^{n+1} - \tilde{F}_{i-1}^{n+1}} \over {\Delta x}} \right) $$
#
# $$ u_i^{n+1} = {1 \over 2} \left( \tilde{u}_i^{n+1} + u_i^n \right) - {\sigma \over 2} \left( {{\tilde{F}_{i}^{n+1} - \tilde{F}_{i-1}^{n+1}}} \right) $$
#
# ## Pseudo-code
# #Constants
# xmax = 4.0
# nx = 51
# dx = xmax/(nx-1)
# tmax = 1.0
# sigma = 1.0
# dt = dx * sigma
# nt = int(tmax/dt + 1)
#
# #Boundary Conditions
# for n between 0 and nt-1
# u(0,n)=1
# u(nx-1,n)=0
#
# #Initial Conditions
# for i between 1 and nx-2
# if(i < (nx-1)/2)
# u(i,0) = 1
# else
# u(i,0) = 0
#
# F = (u/2)**2
#
# #Iteration
# for n between 0 and nt-1
# for i between 1 and nx-2
# up[i,n+1] = u[i,n] - sigma*(F[i+1,n] - F[i,n])
#
# u[i,n+1] = 0.5*(up[i,n+1] + u[i,n]) - 0.5*sigma * (Fp[i,n+1] - Fp[i-1,n+1])
# In[197]:
def maccormack_convection_2(sigma, nx, nt, xmax, step):
"""
Returns the velocity field and distance for 1D linear convection
"""
# Increments
dx = xmax/(nx-1)
dt = dx * sigma
nt = int(tmax/dt + 1)
# Initial and Boundary Conditions
u = initial_and_boundary_conditions(nx, nt)
up = initial_and_boundary_conditions(nx, nt)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
# Lambda function for Flux:
F = lambda u: u**2.0 / 2.0
i = 1
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,nt-1):
up[i:nx-1, n+1] = u[i:nx-1, n] - sigma*(F(u[i+1:nx, n]) - F(u[i:nx-1, n]))
u[i:nx-1, n+1] = ( 0.5*(up[i:nx-1, n+1] + u[i:nx-1, n]) -
0.5*sigma*(F(up[i:nx-1, n+1]) - F(up[i-1:nx-2, n+1])) )
# Wave propagating with speed 0.5
u_lf, x_lf, nt_lf = analytical(nx, tmax, xmax)
dt_lf = dx * 2.0
plot(u,x,nt,u_lf, x_lf, nt_lf, step, dt, dt_lf)
# In[201]:
def plot_schemes(x,u_1,nt_1,u_2,nt_2,u_3,nt_3,u_lf, x_lf, nt_lf, dt_1, dt_2, dt_3, dt_lf):
"""
Plots the 1D velocity field
"""
import matplotlib.pyplot as plt
import matplotlib.cm as cm
plt.figure()
ax=plt.subplot(111)
c_1 = dt_1/(max(x)/100)
c_2 = dt_2/(max(x)/100)
c_3 = dt_3/(max(x)/100)
t_1 =(nt_1-1)*dt_1
ax.plot(x,u_1[:,nt_1-1],linestyle='-',c='r',label='t='+str(t_1)+'Lax-Friedrichs')
t_2 =(nt_2-1)*dt_2
ax.plot(x,u_2[:,nt_2-1],linestyle='-',c='b',label='t='+str(t_2)+'Lax-Wendroff')
t_3 =(nt_3-1)*dt_3
ax.plot(x,u_3[:,nt_3-1],linestyle='-',c='g',label='t='+str(t_3)+'MacCormack')
t_lf =(nt_lf-1)*dt_lf
ax.plot(x_lf,u_lf[:,nt_lf-1],linestyle='--',c='k',label='t='+str(t_lf)+' Analytical')
box=ax.get_position()
ax.set_position([box.x0, box.y0, box.width*2,box.height*2])
ax.legend( bbox_to_anchor=(1.02,1), loc=2)
plt.ylim([-0.5,1.5])
plt.xlim([0.0,4.0])
plt.xlabel('x (m)')
plt.ylabel('u (m/s)')
plt.show()
# In[202]:
def maccormack_convection_cfl(sigma_1, sigma_2, sigma_3, nx, tmax, xmax):
"""
Returns the velocity field and distance for 1D linear convection
"""
# Increments
dx = xmax/(nx-1)
dt_1 = dx * sigma_1
nt_1 = int(tmax/dt_1 + 1)
dt_2 = dx * sigma_2
nt_2 = int(tmax/dt_2 + 1)
dt_3 = dx * sigma_3
nt_3 = int(tmax/dt_3 + 1)
# Initial and Boundary Conditions
u_1 = initial_and_boundary_conditions(nx, nt_1)
u_2 = initial_and_boundary_conditions(nx, nt_2)
u_3 = initial_and_boundary_conditions(nx, nt_3)
up_1 = initial_and_boundary_conditions(nx, nt_1)
up_2 = initial_and_boundary_conditions(nx, nt_2)
up_3 = initial_and_boundary_conditions(nx, nt_3)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
# Lambda function for Flux:
F = lambda u: u**2.0 / 2.0
# Lamba function for Jacobian:
A = lambda u: u
i = 1
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,nt_1-1):
up_1[i:nx-1, n+1] = u_1[i:nx-1, n] - sigma_1*(F(u_1[i+1:nx, n]) - F(u_1[i:nx-1, n]))
u_1[i:nx-1, n+1] = ( 0.5*(up_1[i:nx-1, n+1] + u_1[i:nx-1, n]) -
0.5*sigma_1*(F(up_1[i:nx-1, n+1]) - F(up_1[i-1:nx-2, n+1])) )
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,nt_2-1):
up_2[i:nx-1, n+1] = u_2[i:nx-1, n] - sigma_2*(F(u_2[i+1:nx, n]) - F(u_2[i:nx-1, n]))
u_2[i:nx-1, n+1] = ( 0.5*(up_2[i:nx-1, n+1] + u_2[i:nx-1, n]) -
0.5*sigma_2*(F(up_2[i:nx-1, n+1]) - F(up_2[i-1:nx-2, n+1])) )
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,nt_3-1):
up_3[i:nx-1, n+1] = u_3[i:nx-1, n] - sigma_3*(F(u_3[i+1:nx, n]) - F(u_3[i:nx-1, n]))
u_3[i:nx-1, n+1] = ( 0.5*(up_3[i:nx-1, n+1] + u_3[i:nx-1, n]) -
0.5*sigma_3*(F(up_3[i:nx-1, n+1]) - F(up_3[i-1:nx-2, n+1])) )
# Wave at speed 0.5
u_lf, x_lf, nt_lf = analytical(nx, tmax, xmax)
dt_lf = dx * 2.0
plot_cfl(x,u_1,nt_1,u_2,nt_2,u_3,nt_3,u_lf, x_lf, nt_lf, dt_1, dt_2, dt_3, dt_lf)
# In[203]:
maccormack_convection_cfl(0.25, 0.5, 1.0, 101, 2.0, 4.0)
# ## Conclusion from MacCormack Scheme
#
# $$ \left| {\Delta t \over \Delta x} u_{max} \right| \lt 1 $$
#
# Similar behaviour to the Lax-Wendroff Scheme i.e.
#
# **The higher the CFL number, the more dispersive errors are accumulated**
#
# However, MacCormack is much easier to program
#
# If dispersion errors caused oscillations - the overshoot may make the solution more unstable
# # Which scheme is the best for CFL = 1?
# In[208]:
def scheme_comparison(sigma, nx, tmax, xmax):
"""
Returns the velocity field and distance for 1D linear convection
"""
# Increments
dx = xmax/(nx-1)
dt = dx * sigma
nt = int(tmax/dt + 1)
# Initial and Boundary Conditions
u_1 = initial_and_boundary_conditions(nx, nt)
u_2 = initial_and_boundary_conditions(nx, nt)
u_3 = initial_and_boundary_conditions(nx, nt)
up_3 = initial_and_boundary_conditions(nx, nt)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
# Lambda function for Flux:
F = lambda u: u**2.0 / 2.0
# Lamba function for Jacobian:
A = lambda u: u
i = 1
# Lax-Friedrichs
for n in range(0,nt-1):
u_1[i:nx-1, n+1] = (0.5*(u_1[i-1:nx-2, n]+u_1[i+1:nx, n])-
0.5*sigma*(F(u_1[i+1:nx, n])-F(u_1[i-1:nx-2, n])))
# Lax-Wendroff
for n in range(0,nt-1):
u_2[i:nx-1, n+1] = (u_2[i:nx-1,n] -
0.5*sigma*( F(u_2[i+1:nx, n])-F(u_2[i-1:nx-2, n]) ) +
0.25*(sigma**2)*( ( A(u_2[i+1:nx, n])+A(u_2[i:nx-1, n]) )*
( F(u_2[i+1:nx, n])-F(u_2[i:nx-1, n]) )-
( A(u_2[i:nx-1, n])+A(u_2[i-1:nx-2, n]) )*
( F(u_2[i:nx-1, n])-F(u_2[i-1:nx-2, n]) ) ) )
# MacCormack
for n in range(0,nt-1):
up_3[i:nx-1, n+1] = u_3[i:nx-1, n] - sigma*(F(u_3[i+1:nx, n]) - F(u_3[i:nx-1, n]))
u_3[i:nx-1, n+1] = ( 0.5*(up_3[i:nx-1, n+1] + u_3[i:nx-1, n]) -
0.5*sigma*(F(up_3[i:nx-1, n+1]) - F(up_3[i-1:nx-2, n+1])) )
# Wave at speed 0.5
u_lf, x_lf, nt_lf = analytical(nx, tmax, xmax)
dt_lf = dx * 2.0
plot_schemes(x,u_1,nt,u_2,nt,u_3,nt,u_lf, x_lf, nt_lf, dt, dt, dt, dt_lf)
# In[209]:
scheme_comparison(1.0, 101, 2.0, 4.0)
# ### Conclusion of the Best Scheme
#
# MacCormack is the best scheme:
#
# * less diffusive than Lax-Freidrichs
# * less dispersive than Lax-Wendroff
# # Design Algorithm to Solve Problem using Beam-Warming Scheme
#
# ## Space-time discretisation:
#
# * i $\rightarrow$ index of grid in x
# * n $\rightarrow$ index of time
#
# ## Beam-Warming Numerical scheme
#
# * Taylor Expansion to 2nd order term (replacing $ u_t $ with the average):
#
# $$ u_i^{n+1} = u_i^n + {\Delta t \over 2}[(u_t)_i^n + (u_t)_i^{n+1}] + O(\Delta t^3) $$
#
# * Replace $u_t$ by spatial derivatives:
#
# $$ (u_t)_i^n = - (F_x)_i^n $$
#
# $$ u_i^{n+1} = u_i^n - {\Delta t \over 2}[(F_x)_i^n + (F_x)_i^{n+1}] $$
#
# * Taylor Expansion of $F^{n+1}$
#
# $$ F^{n+1} = F^n + {{\partial F \over \partial t} \Delta t} + O(\Delta t^2) $$
#
# * Replacing the derivative of F with the Jacobian and using **Forward Differencing in time for the velocity gradient** (to avoid a second order derivative in space for F):
#
# $$ {{\partial F} \over {\partial t}} = {{\partial F} \over {\partial u}} {{\partial u} \over {\partial t}} = A_i^n {{\left( {{u_i^{n+1} - u_i^n}} \right)} \over \Delta t} $$
#
# Hence:
#
# $$ F_i^{n+1} = F_i^n + {{A_i^n u_i^{n+1} - A_i^n u_i^n} \over \Delta t} $$
#
# And:
#
# $$ u_i^{n+1} = u_i^n - {\Delta t \over 2}[2(F_x)_i^n + {\partial \over {\partial x}} \left( {A_i^n u_i^{n+1} - A_i^n u_i^n} \right)] $$
#
# * For the first derivative of **Flux** in space: 2nd order CD in space between `i+1` and `i-1`
# * For the first derivative of **Jacobian times the Velocity Difference** in space: 2nd order in space between `i+1` and `i-1`
#
# ## Discrete equation
#
# $$ u_i^{n+1} = u_i^n - {{\Delta t \over 2}} {(F_{i+1}^n - F_{i-1}^n) \over {\Delta x}} - {{\Delta t} \over 2} {({A_{i+1}^n u_{i+1}^{n+1} - A_{i-1}^n u_{i-1}^{n+1}}) \over {2 \Delta x}} -{{\Delta t} \over 2} {({A_{i+1}^n u_{i+1}^n - A_{i-1}^n u_{i-1}^n}) \over {2 \Delta x}} $$
#
# $$ - {\Delta t \over {4 \Delta x}} \left( A_{i-1}^n u_{i-1}^{n+1} \right) +
# u_i^{n+1} + {\Delta t \over {4 \Delta x}} \left( A_{i+1}^n u_{i+1}^{n+1} \right) =
# u_i^n - {1 \over 2} {\Delta t \over \Delta x} (F_{i+1}^n - F_{i-1}^n) +
# {\Delta t \over 4 \Delta x}(A_{i+1}^n u_{i+1}^n - A_{i-1}^n u_{i-1}^n) $$
#
# ## TDMA
#
# For a description of this, see:
#
# http://www.thevisualroom.com/tri_diagonal_matrix.html
#
# ## Pseudo-code
# #Constants
# xmax = 4.0
# nx = 101
# dx = xmax/(nx-1)
# tmax = 2.0
# sigma = 1.0
# dt = dx * sigma
# nt = int(tmax/dt + 1)
#
# #Boundary Conditions
# for n between 0 and nt-1
# u[0,n]=1
# u[nx-1,n]=0
#
# #Initial Conditions
# for i between 1 and nx-2
# if(i < (nx-1)/2)
# u[i,0] = 1
# else
# u[i,0] = 0
#
# #Lambda function
# F = (u/2)**2
#
# #Lamba function
# A = u
#
# #Iteration
# for n between 0 and nt-1
# for i between 1 and nx-2
#
# a[i] = - (dt / (4 * dx)) * A[i-1, n]
#
# b[i] = 1
#
# c[i] = (dt / (4 * dx)) * A[i+1, n]
#
# d[i] = u[i,n] - 0.5 * (dt / dx) * (F[i+1, n] - F[i-1, n]) +
# (dt / (4 * dx)) * (A[i+1, n] * u[i+1, n] - A[i-1, n] * u[i-1, n])
#
# #Corrections for BCs:
#
# d[1] = d[1]-a[1]*B1
# d[nx-2] = d[nx-2]-a[nx-2]*B2
#
# #Built-in solver:
#
# l_and_u = (1, 1)
# abc = np.matrix([c[1:nx-1], b[1:nx-1], a[1:nx-1])
# RHS = d[1:nx-1]
# u[1:nx-1, n] = linalg.solve_banded(l_and_u, abc, RHS)
#
# In[213]:
try:
import numpypy as np # for compatibility with numpy in pypy
except:
import numpy as np # if using numpy in cpython
## Tri Diagonal Matrix Algorithm(a.k.a Thomas algorithm) solver
def TDMAsolver(a, b, c, d):
'''
TDMA solver, a b c d can be NumPy array type or Python list type.
refer to http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm
'''
nf = len(a) # number of equations
ac, bc, cc, dc = map(np.array, (a, b, c, d)) # copy the array
for it in xrange(1, nf):
mc = ac[it]/bc[it-1]
bc[it] = bc[it] - mc*cc[it-1]
dc[it] = dc[it] - mc*dc[it-1]
xc = ac
xc[-1] = dc[-1]/bc[-1]
for il in xrange(nf-2, -1, -1):
xc[il] = (dc[il]-cc[il]*xc[il+1])/bc[il]
del bc, cc, dc # delete variables from memory
return xc
# In[249]:
def plot_oscillatory(u,x,NT,u_analytical, x_analytical, NT2, step, dt, dt_lf):
"""
Plots the 1D velocity field
"""
import matplotlib.pyplot as plt
import matplotlib.cm as cm
plt.figure()
ax=plt.subplot(111)
colour=iter(cm.rainbow(np.linspace(0,step,NT)))
for n in range(0,int(NT),int(step)):
t = n*dt
c=next(colour)
ax.plot(x,u[:,n],linestyle='-',c=c,label='t='+str(t))
t_lf =(NT2-1)*dt_lf
ax.plot(x_analytical,u_analytical[:,NT2-1],linestyle='--',c='k',label='t='+str(t_lf)+' analytical')
box=ax.get_position()
ax.set_position([box.x0, box.y0, box.width*2,box.height*2])
ax.legend( bbox_to_anchor=(1.02,1), loc=2)
plt.ylim([-0.2,1.8])
plt.xlim([0.0,4.0])
plt.xlabel('x (m)')
plt.ylabel('u (m/s)')
plt.show()
# In[260]:
def beam_warming_convection(sigma, nx, tmax, xmax, step, B1):
from scipy import linalg
"""
Returns the velocity field and distance for 1D linear convection
"""
# Increments
dx = xmax/(nx-1)
dt = dx * sigma
nt = int(tmax/dt + 1)
# Initial and Boundary Conditions
u = initial_and_boundary_conditions(nx, nt)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
# Coefficients
a = np.zeros(nx-2)
b = np.ones(nx-2)
c = np.zeros(nx-2)
d = np.zeros(nx-2)
# Lambda function for Flux:
F = lambda u: u**2.0 / 2.0
# Lambda function for Jacobian:
A = lambda u: u
# index of space
i = 1
#index in TDMA
m = 0
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,nt-1):
a[m:nx-2] = - (dt / (4.0 * dx)) * A(u[i-1:nx-2, n])
b[m:nx-2] = 1.0
c[m:nx-2] = (dt / (4.0 * dx)) * A(u[i+1:nx, n])
d[m:nx-2] = ( u[i:nx-1,n] -
0.5 * (dt / dx) * (F(u[i+1:nx, n]) - F(u[i-1:nx-2, n])) +
(dt / (4.0 * dx)) * (A(u[i+1:nx, n]) * u[i+1:nx, n] -
A(u[i-1:nx-2, n]) * u[i-1:nx-2, n]) )
#Correction for BCs (other BCs = 0):
d[m] = ( u[i,n] -
0.5 * (dt / dx) * (F(u[i+1, n]) - F(u[i-1, n])) +
(dt / (4.0 * dx)) * (A(u[i+1, n]) * u[i+1, n] -
A(u[i-1, n]) * u[i-1, n]) - a[m]*B1 )
u[1:nx-1, n+1] = TDMAsolver(a, b, c, d)
# Wave at speed 0.5
u_lf, x_lf, nt_lf = analytical(nx, tmax, xmax)
dt_lf = dx * 2.0
plot_oscillatory(u,x,nt,u_lf, x_lf, nt_lf, step, dt, dt_lf)
# ## Beam-Warming with CFL = 1
# In[268]:
beam_warming_convection(1.0, 101, 2.0, 4.0, 5, 1.0)
# ## What happens if the grid density is increased?
#
# * Oscillation density increases
# * Scheme is still stable, but oscillatory
# In[300]:
beam_warming_convection(1.0, 201, 2.0, 4.0, 5, 1.0)
# ## Conclusions from Beam-Warming
#
# The method is stable but oscillatory because of the absense of damping, so introduce a damping function onto the RHS:
# $$ D_e = - \epsilon_e(u_{i+2}^n - 4u_{i+1}^n + 6u_i^n - 4u_{i-1}^n + u_{i-2}^n) $$
#
# For the **solution point** $i=1$, the value of $u_{-1}$ is unknown, so set it to the value at $u_{0}$ (as no periodic boundaries are used).
#
# For the **solution point** $i=nx-2$ the value of $u_{nx}$ is unknown, so set it to the value at $u_{nx-1}$ (for the same reason)
#
#
# ## Pseudo Code
# #Constants
# epsilon = 0.125
#
# for i between 1 and nx-2
# ipos[i]=i+2
# ineg[i]=i-2
#
# ipos[nx-2]=nx-1
# ineg[1]=0
#
# for i between 1 and nx-2
#
# D[i] = -epsilon*(u[ipos[i],n] - 4*u[i+1,n] + 6*u[i,n] - 4*u[i-1,n] + u[ineg[i],n])
#
# # D[i] = -epsilon*(u[i+2,n] - 4*u[i+1,n] + 6*u[i,n] - 4*u[i-1,n] + u[i-2,n])
# In[244]:
def damping_function(u, epsilon, nx, n):
ipos = np.zeros(nx, dtype=numpy.int)
ineg = np.zeros(nx, dtype=numpy.int)
for i in range(1,nx-1):
ipos[i] = i+2
ineg[i] = i-2
# Set the values at the extremities to the boundary values
ipos[nx-2] = nx-1
ineg[1] = 0
D = np.zeros(nx)
i = 1
D[i:nx-1] = epsilon * (u[ipos[i:nx-1],n] - 4*u[i+1:nx,n] +
6*u[i:nx-1,n] - 4*u[i-1:nx-2,n] + u[ineg[i:nx-1],n])
return D
# In[295]:
def beam_warming_convection_damping(sigma, nx, tmax, xmax, step, B1, epsilon):
from scipy import linalg
"""
Returns the velocity field and distance for 1D linear convection
"""
# Increments
dx = xmax/(nx-1)
dt = dx * sigma
nt = int(tmax/dt + 1)
# Initial and Boundary Conditions
u = initial_and_boundary_conditions(nx, nt)
# X Loop
x = np.zeros(nx)
x = np.linspace(0.0,xmax,nx)
# Coefficients
a = np.zeros(nx-2)
b = np.ones(nx-2)
c = np.zeros(nx-2)
d = np.zeros(nx-2)
# Lambda function for Flux:
F = lambda u: u**2.0 / 2.0
# Lambda function for Jacobian:
A = lambda u: u
# index of space
i = 1
#index in TDMA
m = 0
# Loop - must loop as NumPy cannot be used for dependency reasons
for n in range(0,nt-1):
D = damping_function(u, epsilon, nx, n)
a[m:nx-2] = - (dt / (4.0 * dx)) * A(u[i-1:nx-2, n])
b[m:nx-2] = 1.0
c[m:nx-2] = (dt / (4.0 * dx)) * A(u[i+1:nx, n])
d[m:nx-2] = ( u[i:nx-1,n] -
0.5 * (dt / dx) * (F(u[i+1:nx, n]) - F(u[i-1:nx-2, n])) +
(dt / (4.0 * dx)) * (A(u[i+1:nx, n]) * u[i+1:nx, n] -
A(u[i-1:nx-2, n]) * u[i-1:nx-2, n]) - D[i:nx-1] )
#Correction for BCs (other BCs = 0):
d[m] = ( u[i,n] -
0.5 * (dt / dx) * (F(u[i+1, n]) - F(u[i-1, n])) +
(dt / (4.0 * dx)) * (A(u[i+1, n]) * u[i+1, n] -
A(u[i-1, n]) * u[i-1, n]) - D[i] - a[m]*B1 )
u[1:nx-1, n+1] = TDMAsolver(a, b, c, d)
# Wave at speed 0.5
u_lf, x_lf, nt_lf = analytical(nx, tmax, xmax)
dt_lf = dx * 2.0
plot_oscillatory(u,x,nt,u_lf, x_lf, nt_lf, step, dt, dt_lf)
# ## What is the effect of damping?
#
# * Damping reduces the oscillatory response, although the scheme is implicitly stable
# In[303]:
beam_warming_convection_damping(0.5, 101, 2.0, 4.0, 10, 1.0, 0.125)
# ## What happens if the BC on the LHS is set to zero?
# * Solution is invalid
# In[298]:
beam_warming_convection_damping(0.5, 101, 2.0, 4.0, 10, 0.0, 0.125)
# In[ ]: