Transient Fickian Diffusion with Reaction#
OpenPNM
supports adding reaction terms to both steady state and transient simulations. OpenPNM
already includes many different source term models that can be added to simulate a reaction. In this example, we show how to add a powerlaw
source term model to a transient fickian diffusion simulation.
Start by importing openpnm
[1]:
import openpnm as op
%config InlineBackend.figure_formats = ['svg']
import matplotlib.pyplot as plt
import numpy as np
ws = op.Workspace()
proj = ws.new_project()
np.random.seed(10)
Define network, geometry, and phase objects#
[2]:
shape = [25, 25, 1]
net = op.network.Cubic(shape=shape, spacing=1e-2)
geo = op.geometry.SpheresAndCylinders(network=net, pores=net.Ps, throats=net.Ts)
phase = op.phases.Water(network=net)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Input In [2], in <cell line: 4>()
2 net = op.network.Cubic(shape=shape, spacing=1e-2)
3 geo = op.geometry.SpheresAndCylinders(network=net, pores=net.Ps, throats=net.Ts)
----> 4 phase = op.phases.Water(network=net)
AttributeError: module 'openpnm' has no attribute 'phases'
You can adjust the diffusion coefficient by calling the pore.diffusivity
dictionary key on the phase object and then setting it’s value.
[3]:
phase['pore.diffusivity'] = 2e-07
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [3], in <cell line: 1>()
----> 1 phase['pore.diffusivity'] = 2e-07
NameError: name 'phase' is not defined
Define physics object#
Here, we will use Standard
physics which already includes many standard models. You could also use Generic
physics and add neccessary models like throat.diffusive_conductance
after using phys.add_model()
method.
[4]:
phys = op.physics.Standard(network=net, phase=phase, geometry=geo)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [4], in <cell line: 1>()
----> 1 phys = op.physics.Standard(network=net, phase=phase, geometry=geo)
NameError: name 'phase' is not defined
Add reaction model#
Add reaction model to physics object. For this example we use the powerlaw
reaction model which is of the form:
\begin{equation*} \ A_1X^{A_2} \end{equation*}
[5]:
phase['pore.concentration'] = 0
phys['pore.rxnA'] = -1e-10
phys['pore.rxnb'] = 1
phys.add_model(propname='pore.reaction', model=op.models.physics.generic_source_term.power_law,
A1='pore.rxnA', A2='pore.rxnb', X='pore.concentration')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [5], in <cell line: 1>()
----> 1 phase['pore.concentration'] = 0
2 phys['pore.rxnA'] = -1e-10
3 phys['pore.rxnb'] = 1
NameError: name 'phase' is not defined
Define transient fickian diffusion object#
[6]:
tfd = op.algorithms.TransientFickianDiffusion(network=net, phase=phase)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [6], in <cell line: 1>()
----> 1 tfd = op.algorithms.TransientFickianDiffusion(network=net, phase=phase)
NameError: name 'phase' is not defined
Setup the transient algorithm settings#
To do this you can call the tfd.setup()
method and use it to set settings such as transient solver scheme, final time, time step, tolerance, etc. The cranknicolson
scheme used here is the most accurate but slowest. Other time schemes are implicit
which is faster but less accurate and steady
which gives the steady state solution.
[7]:
x0 = 0
tspan = (0, 2000)
saveat = 100
Set value boundary conditions#
In this example we set the concentraton of the front
pores to 1 and the concentration of the back
pores to 0.
[8]:
tfd.set_value_BC(pores=net.pores('front'), values=1)
tfd.set_value_BC(pores=net.pores('back'), values=0)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [8], in <cell line: 1>()
----> 1 tfd.set_value_BC(pores=net.pores('front'), values=1)
2 tfd.set_value_BC(pores=net.pores('back'), values=0)
NameError: name 'tfd' is not defined
Set initial condition#
Initial conditions must be specified when alg.run
is called as alg.run(x0=x0)
, where x0
could either be a scalar (in which case it’ll be broadcasted to all pores), or an array.
Set source term#
In this example, we apply the source term to pores 212 and 412.
[9]:
tfd.set_source(propname='pore.reaction', pores=[212, 412])
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [9], in <cell line: 1>()
----> 1 tfd.set_source(propname='pore.reaction', pores=[212, 412])
NameError: name 'tfd' is not defined
Run the simulation#
[10]:
tfd.run(x0=x0, tspan=tspan, saveat=saveat)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [10], in <cell line: 1>()
----> 1 tfd.run(x0=x0, tspan=tspan, saveat=saveat)
NameError: name 'tfd' is not defined
Visualize results#
Ater simulation runs we can visualize results using a colour plot from matplotlib.pyplot
. Here we visualize the results at the final time t_final
by using the pore.concentration
key on the algorithm object.
[11]:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
c = tfd['pore.concentration'].reshape(shape)
fig, ax = plt.subplots(figsize=(5, 5))
im = ax.imshow(c[:,:,0])
ax.set_title(f'concentration (mol/m$^3$) @ t = {tspan[1]}')
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="4%", pad=0.1)
plt.colorbar(im, cax=cax);
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [11], in <cell line: 4>()
1 import matplotlib.pyplot as plt
2 from mpl_toolkits.axes_grid1 import make_axes_locatable
----> 4 c = tfd['pore.concentration'].reshape(shape)
5 fig, ax = plt.subplots(figsize=(5, 5))
6 im = ax.imshow(c[:,:,0])
NameError: name 'tfd' is not defined
If we print the TransientFickianDiffusion
object we can see a list of the object’s properties. Notice how the concentration is recorded here for each output concentration, t_output
.
[12]:
print(tfd)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [12], in <cell line: 1>()
----> 1 print(tfd)
NameError: name 'tfd' is not defined
We can visualize intermediate concentration profiles using a colour plot but use pore.concentration@100
(or similar) as the dictionary key on the algorithm object.
[13]:
c = tfd.soln(100).reshape(shape)
fig, ax = plt.subplots(figsize=(5, 5))
im = ax.imshow(c[:,:,0])
ax.set_title(f'concentration (mol/m$^3$) @ t = 100')
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="4%", pad=0.1)
plt.colorbar(im, cax=cax);
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [13], in <cell line: 1>()
----> 1 c = tfd.soln(100).reshape(shape)
2 fig, ax = plt.subplots(figsize=(5, 5))
3 im = ax.imshow(c[:,:,0])
NameError: name 'tfd' is not defined