NSSC/Exercise_03/task01.py

155 lines
5.0 KiB
Python

import numpy as np
from scipy.sparse import spdiags
from scipy.linalg import solve_banded
from matplotlib import pyplot as plt
# Config
D = 1e-6 # diffusion coefficient
h = 1 # space domain (max x size)
T = 2e5 # solution end time
nx = 50 # nr of space discretization points
nt = 1000 # nr of time discretization points
# derived constants
dx = h / (nx - 1) # space step size
dt = T / nt # time step size
d = dt * D / dx**2 # stability/stepsize coefficient
# Task 1.1 equation system matrix `C^n+1 = A1 C^n`
def A1(d):
a1 = np.zeros([3, nx])
# superdiagonal (above the main diagonal)
a1[0, 1] = 0
a1[0, 2:] = d
# main diagonal
a1[1, 0] = 1
a1[1, 1:] = 1 - 2 * d
# subdiagonal (below the main diagonal)
a1[2, :-2] = d
a1[2, -2] = 2 * d
# convert to sparse tridiagonal matrix
return spdiags(a1, [1, 0, -1], nx, nx)
# Task 1.2 equation system matrix `C^n+1 = A2 C^n`
def A2(d):
a2 = A1(d).data
# addapt boundary condition coefficients, the rest is identical
a2[0, -1] = a2[1, -1] = a2[2, -2] = 0
# convert to sparse tridiag
return spdiags(a2, [1, 0, -1], nx, nx)
# Task 1.3 equation system matrix for update rule `A3 C^n+1 = C^n`
def A3(d):
a3 = np.zeros([3, nx])
# superdiagonal
a3[0, 1] = 0
a3[0, 2:] = -d
# main diagonal
a3[1, 0] = 1
a3[1, 1:] = 1 + 2 * d
# supdiagonal
a3[2, :-2] = -d
a3[2, -2] = -2 * d
# as tridiag
return spdiags(a3, [1, 0, -1], nx, nx)
# Task 1.4 equation system matrix `A4`` in `(I + A4) C^n+1 = (I - A4) C^n`
def A4(d):
a4 = np.zeros([3, nx])
# superdiagonal
a4[0, 2:] = -d / 2
# main diagonal
a4[1, 1:] = d
# supdiagonal
a4[2, :-2] = -d / 2
a4[2, -2] = -d
return spdiags(a4, [1, 0, -1], nx, nx)
# setup `I + A4` and `I - A4`
def IpA4(d):
ipA4 = A4(d).data
ipA4[1, ] += 1
return spdiags(ipA4, [1, 0, -1], nx, nx)
def ImA4(d):
imA4 = -A4(d).data
imA4[1, ] += 1
return spdiags(imA4, [1, 0, -1], nx, nx)
# plots a solution to file, simply for code size reduction in `solve`
def plot(x, C, name):
plt.clf()
plt.plot(x, C)
plt.xlim([0, h])
plt.ylim([0, 1.2])
plt.title(name)
plt.xlabel("Distance from Source: x")
plt.ylabel("Concentration: C(x, t)")
plt.savefig(f"plots/{name}_{plot.i:0>5}.png")
plot.i += 1
plot.i = 0
# solver for `C_t - D C_xx = 0` given discretized equation system matrices for
# a time update rule `L C^n+1 = R C^n` starting from an initial solution `C`.
# The update is iterated for `nt` time steps corresponding to a solution at
# time `T = dt nt`
def solve(C, L, R, nt, plotname = None, plotskip = 5):
if plotname is not None:
plot.i = 0 # (re)set plot counter
x = np.linspace(0, h, nx) # space grid points
fig = plt.figure(figsize = (8, 6), dpi = 100) # setup plot figure size
plot(x, C, plotname) # plot initial solution
for t in range(nt):
rhs = C if R is None else R @ C
C = rhs if L is None else solve_banded((1, 1), L.data, rhs)
if plotname is not None and ((t + 1) % plotskip == 0):
plot(x, C, plotname) # plot current solution
return C
# setup initial solution
C0 = np.zeros(nx)
C0[0] = 1
# run simulation and generate plots for each subtask
C1 = solve(C0, None, A1(d), nt, "task01_1")
Cx = solve(C0, None, A1(0.55), 200, "task01_x", 1) # run with unstable `d > 0.5`
C2 = solve(C0, None, A2(d), nt, "task01_2")
C3 = solve(C0, A3(d), None, nt, "task01_3")
C4 = solve(C0, IpA4(d), ImA4(d), nt, "task01_4")
# to convert generated image sequence to video use:
# $> ffmpeg -y -r 60 -i plots/task01_1_%05d.png -pix_fmt yuv420p video_1_1.mp4
# $> ffmpeg -y -r 60 -i plots/task01_x_%05d.png -pix_fmt yuv420p video_1_x.mp4
# $> ffmpeg -y -r 60 -i plots/task01_2_%05d.png -pix_fmt yuv420p video_1_2.mp4
# $> ffmpeg -y -r 60 -i plots/task01_3_%05d.png -pix_fmt yuv420p video_1_3.mp4
# $> ffmpeg -y -r 60 -i plots/task01_4_%05d.png -pix_fmt yuv420p video_1_4.mp4
# compute till no time change, e.g. `t -> inf`
C1inf = solve(C0, None, A1(d), 20000)
C2inf = solve(C0, None, A2(d), 20000)
x = np.linspace(0, h, nx) # space grid points
fig = plt.figure(figsize = (8, 6), dpi = 100) # setup plot figure size
plot.i = 0 # reset plot counter
plot(x, C1inf, "Cinf")
plot(x, C2inf, "Cinf")
# # All plots in one
C1 = solve(C0, None, A1(d), nt)
C2 = solve(C0, None, A2(d), nt)
C3 = solve(C0, A3(d), None, nt)
C4 = solve(C0, IpA4(d), ImA4(d), nt)
x = np.linspace(0, h, nx) # space grid points
plt.figure(figsize = (8, 6), dpi = 100) # setup plot figure size
plt.plot(x, C1, label = "1.1")
plt.plot(x, C2, label = "1.2")
plt.plot(x, C3, label = "1.3")
plt.plot(x, C4, label = "1.4")
plt.xlim([0, h])
plt.ylim([0, 1.2])
plt.title("All")
plt.legend()
plt.xlabel("Distance from Source: x")
plt.ylabel("Concentration: C(x, t)")
plt.savefig(f"plots/task01_all.png")