SUPG advection-diffusion of a Gaussian in an interval

SUPG advection-diffusion of a Gaussian in an interval#

Donea, J. & Huerta, A. (2003). Finite Element Methods for Flow Problems. \(\S 5.6.1\)

\[\begin{split} \mathbb{S} \begin{cases} \Omega = [0, 1] & \text{unit interval} \\ u_0(x) = \mu\exp\left(-(x-x_0)^2/\sigma^2\right) & \text{Gaussian initial condition} \\ u_{\text{D}}(x=0,1)=0 & \text{Dirichlet boundary conditions} \\ \textbf{a}=a\,\textbf{e}_x & \text{constant velocity} \\ \mathsf{D}=D\mathsf{I} & \text{constant isotropic diffusivity} \\ R=0 & \text{zero reaction} \\ J=0 & \text{zero source} \\ \hline u_{\text{e}}(x, t)=\frac{\mu}{\sqrt{1+4Dt/\sigma^2}}\exp\left(-\frac{(x-x_0-at)^2}{\sigma^2+4Dt}\right) & \text{exact solution} \\ Pe = \frac{a}{2DN_x} & \text{local Peclet number} \end{cases} \end{split}\]
import numpy as np
from lucifex.mesh import interval_mesh
from lucifex.fem import Constant
from lucifex.fdm import (
    CN, advective_timestep, peclet_argument, FiniteDifference, FunctionSeries, ConstantSeries, finite_difference_order,
)
from lucifex.solver import ibvp , BoundaryConditions
from lucifex.sim import Simulation, run
from lucifex.viz import plot_line, save_figure
from lucifex.utils import as_index, nested_dict
from lucifex.pde.advection_diffusion import advection_diffusion

def exact_solution(
    x,
    t,
    a,
    d,
    sigma,
    mu,
    x0,
):
    factor = (mu / np.sqrt(1 + 4 * d * t / sigma**2)) 
    func = np.exp(-(x - x0 - a * t)**2 / (sigma**2 + 4 * d * t))
    return factor * func


def create_simulation(
    supg: str | None,
    Lx: float,
    Nx: int,
    dt: float,
    a: float,
    d: float,
    D_adv: FiniteDifference,
    D_diff: FiniteDifference,
    mu: float,
    x0: float,
    sigma: float,
    cache_matrix: bool = False,
) -> Simulation:
    order = finite_difference_order(D_adv, D_diff)
    mesh = interval_mesh(Lx, Nx)
    t = ConstantSeries(mesh, name='t', ics=0.0)
    dt = Constant(mesh, dt, name='dt')
    a = Constant(mesh, (a, ), name='a')
    d = Constant(mesh, d, name='d')
    u = FunctionSeries((mesh, 'P', 1), name='u', order=order, store=1)
    ics = lambda x: exact_solution(x[0], 0.0, a.value[0], d.value, sigma, mu, x0)
    bcs = BoundaryConditions(
        ('dirichlet', lambda x: x[0], 0.0),
        ('dirichlet', lambda x: x[0] - Lx, 0.0),
    )
    u_solver = ibvp(advection_diffusion, ics, bcs, cache_matrix=cache_matrix)(
        u, dt, a, d, D_adv, D_diff, supg=supg,
    )
    return Simulation([u_solver], t, dt)

Lx = 1.0
Nx = 100
h = Lx / Nx

a = 1.0
courant = 0.5
dt = advective_timestep(a, h, courant)

D_adv = CN
D_diff = CN

mu = 5/7
x0 = 2/15
sigma = 7 * np.sqrt(2) / 300

Pe_opts = (1.0, 5.0, 100.0)
supg_opts = (None, 'coth', 'shakib', 'codina')
simulations = nested_dict((str, float, Simulation))

for supg in supg_opts:
    for Pe in Pe_opts:
        d = peclet_argument(Pe, h=h, a=a)
        simulations[supg][Pe] = create_simulation(supg, Lx, Nx, dt, a, d, D_adv, D_diff, mu, x0, sigma)

t_stop = 0.7
for supg in supg_opts:
    for Pe in Pe_opts:
        run(simulations[supg][Pe], t_stop=t_stop) 

x = np.linspace(0, Lx, num=500)
t_target = 0.6
for Pe in Pe_opts:
    legend_title = '\n'.join(
        (
            f'$C_{{\mathbf{{a}}}}={courant}$',
            f'$Pe={Pe:.1f}$',
            f'$\mathcal{{D}}_{{\mathbf{{a}}}}=\mathrm{{{D_adv.name}}}$',
            f'$\mathcal{{D}}_{{\mathsf{{D}}}}=\mathrm{{{D_diff.name}}}$',
            '\nSUPG',
        )
    )
    d = peclet_argument(Pe, h=h, a=a)
    ue = exact_solution(x, t_target, a, d, sigma, mu, x0)
    u0 = exact_solution(x, 0.0, a, d, sigma, mu, x0)
    lines = [(x, ue), (x, u0)]
    legend_labels = ['exact', 'initial']
    for supg in supg_opts:
        u = simulations[supg][Pe]['u']
        time_index = as_index(u.time_series, t_target, func=lambda x, y: np.isclose(x, y))
        t = u.time_series[time_index]
        lines.append(u.series[time_index])
        if supg is None:
            supg = 'unstabilized'
        if supg in ('shakib', 'codina'):
            supg = supg.capitalize()
        legend_labels.append(supg)    
    fig, ax = plot_line(
        lines, legend_labels, legend_title, 
        x_lims=x, 
        x_label='$x$', 
        y_label=f'$u(x,t={t:.2f})$',
    )
    thumbnail = (Pe is Pe_opts[-1])
    save_figure(f'Pe={Pe:.1f}_C_{{\mathbf{{a}}}}={courant}_Dadv={D_adv.name}_Ddiff={D_diff.name}', thumbnail=thumbnail)(fig)
../../_images/5904b365da9678a6309206e00935ab2bd70d6e0f8705d50285a8b311d17d2afa.png ../../_images/874627678a8d601d1858e5dd5d46b3aa45505ece648d05dad0e03e188d7d2556.png ../../_images/312a814086ae81a113fc9a48df7c204c229b1f9fc2ddab1c5558e930163b0052.png