Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Unsteady diffusion + reaction 1/2/3D

Reaction: A→B→CA \rightarrow B\rightarrow C

∂cA∂t+div(−D grad(cA))=−k1 cA\frac{\partial c_A}{\partial t} + \mathrm{div}(-D \, \mathrm{grad}(c_A)) = -k_1 \, c_A

∂cB∂t+div(−D grad(cB))=k1 cA−k2 cB\frac{\partial c_B}{\partial t} + \mathrm{div}(-D \, \mathrm{grad}(c_B)) = k_1 \, c_A - k_2 \, c_B

∂cC∂t+div(−D grad(cC))=k2 cB\frac{\partial c_C}{\partial t} + \mathrm{div}(-D \, \mathrm{grad}(c_C)) = k_2 \, c_B

with boundary conditions: cA=cw,cB=0,cC(0)=0c_A= c_w , c_B=0, c_C(0)=0

The setup of this problem is more complex than is stricktly necesarry. The puprose is to demonstrate that models can be build in such a way that they can be used in 1, 2 or 3 dimensions. For 1D and 2D it was found that the direct sparse LU solver works well. For 3D it seems very slow and BiCGStab with an ILU preconditioner is used. Note that the ILU preconditioner takes some time in this case.

All boundaries use a Dirichlet condition, {'a': 0, 'b': 1, 'd': c_b}, applied with the outward normal convention (see the conventions skill’s pitfalls note P1), so the sign of the boundary flux contribution flips between opposite faces of the domain.

Imports

Reaction term and parameters

Grid, operators and Jacobian assembly

Build the grid and the diffusion operators per spatial axis, accumulate them into a constant Jacobian jac and boundary term g_const, subtract the (linear) reaction Jacobian, and factorize jac once (LU for dim != 3, ILU-preconditioned for dim == 3).

Plot setup

<Figure size 800x200 with 4 Axes>

Time stepping

<Figure size 800x200 with 4 Axes>

Checking the result

The reaction term is linear in c, so the whole implicit-Euler step is linear too: jac and g_const were assembled once and reused for every time step, and they define a residual

g(c)=gconst+jac c−cold/dtg(c) = g_\mathrm{const} + \mathrm{jac}\, c - c_\mathrm{old}/dt

whose exact Jacobian is jac itself. The linear systems are solved iteratively, so evaluating this residual with pymrm.checks.residual_check shows whether the last linear solve converged to a scale-free backward error. It cannot catch a mis-assembled operator: the residual is built from the same matrices.

Note that c_old still holds the state from the last time step actually taken in the loop above (it is reassigned to c.copy() at the top of each iteration), so this checks the step from that state to the final c, not a step starting from c itself.

{'ok': True, 'backward_error': 2.955238379259546e-16, 'max_abs_residual': 8.526512829121202e-14}
Residual check passed: the assembled operators are consistent with the last time step taken.