Skip to content

Commit

Permalink
Merge branch 'main' into joss
Browse files Browse the repository at this point in the history
  • Loading branch information
tlroy committed May 19, 2024
2 parents 870a048 + c256042 commit 39080b3
Show file tree
Hide file tree
Showing 15 changed files with 549 additions and 24 deletions.
11 changes: 11 additions & 0 deletions demos/demo_references.bib
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@article{gupta2006calculation,
title={Calculation for the cathode surface concentrations in the electrochemical reduction of {CO}₂ in {KHCO}₃ solutions},
author={Gupta, N and Gattrell, M and MacDougall, B},
journal={Journal of applied electrochemistry},
volume={36},
number={2},
pages={161--172},
year={2006},
publisher={Springer},
doi={10.1007/s10800-005-9058-y}
}
Binary file added demos/flow_reactor/CO2_solution_field.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demos/flow_reactor/flow_reactor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
311 changes: 311 additions & 0 deletions demos/flow_reactor/flow_reactor.py.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
Flow reactor for CO2 electrolysis
=================================

We consider a flowing bicarbonate elecrolyte with CO2 reduction on a flat plate
electrode.

.. image:: flow_reactor.png
:width: 400px
:align: center


We consider simplified CO2 - KHCO3 homogeneous reactions:

.. math::
\ce{CO2} + \ce{OH-} \xrightleftharpoons[k_1^b]{k_1^f} \ce{HCO3-}
\ce{HCO3-} + \ce{OH-} \xrightleftharpoons[k_2^b]{k_2^f} \ce{CO3^2+} + \ce{H2O}
where :math:`k_i^f` and :math:`k_i^b` are the forward and backward rate constants, respectively.
Here, we assume the dilute solution case where :math:`\ce{H2O}`
concentration is not tracked.

We are solving for :math:`c_k`, the molar concentration of species
:math:`k\in\{\ce{CO2}, \ce{HCO3-}, \ce{CO3^2+}, \ce{OH-},
\ce{K+}\}`,
as well as :math:`\Phi_2`, the ionic potential.
For each species :math:`k`, the conservation of mass is given by

.. math::
\nabla \cdot \mathbf{N}_k = R_k,
where :math:`\mathbf N_k` and :math:`R_k` are the flux and homogeneous
reactions for species :math:`k`, respectively The flux term is given by the
Nernst-Planck equation, which includes diffusion, advection, and
electromigration:

.. math::
\mathbf N_k = -D_k \nabla c_k + c_k \mathbf u - z_k F \frac{D_k}{RT} c_k \nabla \Phi_2,
where :math:`D_k` is the diffusion coefficient of species :math:`k`, and
:math:`z_k`, its charge number, :math:`\mathbf{u}` is the velocity, :math:`F`
is the Faraday constant, :math:`R` is the ideal gas constant, :math:`T` is the
temperature. We assume shear flow such that the velocity field is given by
:math:`\mathbf{u} = (\dot \gamma y, 0)`, where :math:`\dot\gamma` is the shear rate and
:math:`y` is the distance from the electrode.

We assume the electroneutrality approximation such that

.. math::
\sum_k z_k c_k = 0.
The are five charge-transfer reactions at the surface of the electrode

.. math::
\ce{CO2} + \ce{H2O} + 2\ce{e-} \rightleftharpoons \ce{HCOO-} + \ce{OH-}
\ce{CO2} + \ce{H2O} + 2\ce{e-} \rightleftharpoons \ce{CO} + 2\ce{OH-}
\ce{CO2} + 6\ce{H2O} + 8\ce{e-} \rightleftharpoons \ce{CH4} + 8\ce{OH-}
2\ce{CO2} + 8\ce{H2O} + 12\ce{e-} \rightleftharpoons \ce{C2H4} + 12\ce{OH-}
2\ce{H2O} + 2\ce{e-} \rightleftharpoons \ce{H2} + 2\ce{OH-}
Note that we ignore the transport of products other than :math:`\ce{OH-}`. For each
reaction :math:`j`, we assume a constant current density :math:`i_j`, such that
the normal flux at the electrode surface is given by

.. math::
\mathbf{N}_k \cdot \mathbf n = -\sum_j \frac{s_{k,j} i_j}{n_j F},
where :math:`s_{k,j}` is the stoichiometry coefficient for the species
:math:`k` in reaction :math:`j`, and :math:`n_j` is the number of electrons
transferred. The partial current density is given by :math:`i_j= \mathrm{cef}_j
i_\mathrm{tot}`, where :math:`\mathrm{cef}_j` is the current efficiency of
reaction :math:`j` and :math:`i_\mathrm{tot}` is the total current.

At the inlet, the boundary condition for each species :math:`k` is

.. math::
\mathbf N_k \cdot \mathbf n = c_{k,\mathrm{bulk}} \mathbf u \cdot \mathbf n,
where :math:`c_{k,\mathrm{bulk}}` is the bulk concentration, and at the outlet

.. math::
\mathbf N_k \cdot \mathbf n = c_k \mathbf u \cdot \mathbf n.
At the top boundary, i.e. the bulk, the ionic potential is set to a reference
value :math:`\Phi_2=0`, and a no-flux condition is set for all species. A no
flux-condition is also set on the walls with no electrode, as well as on the
electrode for the species that are not reacting there.

The species parameters, homogeneous bulk reactions, and the constant-rate
charge-transfer kinetics are taken from :cite:`gupta2006calculation`.
For this demo, we will use SI units. EchemFEM has no notion of units and the
user simply must provide consistent units.

We import the required packages::

from firedrake import *
from echemfem import EchemSolver, RectangleBoundaryLayerMesh

Then, we create our own child class of :py:class:`EchemSolver <echemfem.EchemSolver>`::

class GuptaSolver(EchemSolver):
def __init__(self):
We now define a rectangular mesh with added refinement in a thin boundary layer
along the electrode boundary using :py:func:`RectangleBoundaryLayerMesh <echemfem.utility_meshes.RectangleBoundaryLayerMesh>`. The
channel is of width 1 mm and length 5 mm. We have 100 elements in the length
direction. In the width direction, there are 50 elements in the first
micrometer and 50 elements for the rest. The local refinement is meant to
capture the thin :math:`\ce{OH-}` boundary layer. The electrode is on the
bottom boundary, which has boundary marker 3::

Ly = 1e-3 # m
Lx = 5e-3 # m
mesh = RectangleBoundaryLayerMesh(100, 50, Lx, Ly, 50, 1e-6, boundary=(3,))

For numerical reasons, we do not want the electrode boundary to be right next
to the inlet and outlet regions. We will have 1 mm of inactive wall before and
after the electrode. One easy way to do this for Neumann boundaries is to
define an indicator function for the electrode using spatial coordinates and
conditional given by Firedrake/UFL::

x, y = SpatialCoordinate(mesh)
active = conditional(And(x >= 1e-3, x < Lx-1e-3), 1., 0.)

We create a list with parameters for all species. Each entry in the list is a
dictionary containing the name, the diffusion coefficient, the bulk
concentration, and charge number of a species::

conc_params = []

conc_params.append({"name": "CO2",
"diffusion coefficient": 19.1e-10, # m^2/s
"bulk": 34.2, # mol/m3
"z": 0,
})

conc_params.append({"name": "HCO3",
"diffusion coefficient": 9.23e-10, # m^2/s
"bulk": 499., # mol/m3
"z": -1,
})

conc_params.append({"name": "CO3",
"diffusion coefficient": 11.9e-10, # m^2/s
"bulk": 7.6e-1, # mol/m3
"z": -2,
})

conc_params.append({"name": "OH",
"diffusion coefficient": 52.7e-10, # m^2/s
"bulk": 3.3e-4, # mol/m3
"z": -1,
})

conc_params.append({"name": "K",
"diffusion coefficient": 19.6E-10, # m^2/s
"bulk": 499. + 7.6e-1 + 3.3e-4, # mol/m3
"z": 1,
})

Similarly, we provide a list containing the parameters for the homogeneuous
reactions. Each entry is a dictionary for one reaction containing: a dictionary
with the stoichiometry of all reactants (negative) and all products (positive),
and rate constants::

homog_params = []

homog_params.append({"stoichiometry": {"CO2": -1,
"OH": -1,
"HCO3": 1,
},
"forward rate constant": 5.93, # m3/mol/s
"backward rate constant": 1.34e-4 # 1/s
})

homog_params.append({"stoichiometry": {"HCO3": -1,
"OH": -1,
"CO3": 1,
},
"forward rate constant": 1e5, # m3/mol/s
"backward rate constant": 2.15e4 # 1/s
})

For convenience, we write a function that will return the current density
function for each charge-transfer reaction. To have zero-flux on the inactive
walls, we multiply by our indicator function. Since the currents are constant,
the argument is unused::

def current(cef):
j = 50. # A/m2
def curr(u):
return cef * j * active
return curr

We create a list for the parameters of the charge-transfer reactions where each
entry is a dictionary associated with a reaction. For the key ``"reaction"`` we
provide a function that returns the partial current density of the reaction.
The stoichiometry is provided similarly to ``homog_params``. We also provide
the number of electrons transferred and name the boundary where the reaction
happens::

echem_params = []

echem_params.append({"reaction": current(0.1), # HCOO
"stoichiometry": {"CO2": -1,
"OH": 1
},
"electrons": 2,
"boundary": "electrode",
})

echem_params.append({"reaction": current(0.05), # CO
"stoichiometry": {"CO2": -1,
"OH": 2
},
"electrons": 2,
"boundary": "electrode",
})

echem_params.append({"reaction": current(0.25), # CH4
"stoichiometry": {"CO2": -1,
"OH": 8
},
"electrons": 8,
"boundary": "electrode",
})

echem_params.append({"reaction": current(0.2), # C2H4
"stoichiometry": {"CO2": -2,
"OH": 12
},
"electrons": 12,
"boundary": "electrode",
})

echem_params.append({"reaction": current(0.4), # H2
"stoichiometry": {"OH": 2
},
"electrons": 2,
"boundary": "electrode",
})

Most physical parameters that are not associated with species or reaction are
passed through the ``physical_params`` argument in a dictionary. The ``"flow"`` key
is given a list of the desired transport mechanisms. The other parameters are
physical constants described above::

physical_params = {"flow": ["diffusion",
"electroneutrality",
"migration",
"advection"],
"F": 96485.3329, # C/mol
"R": 8.3144598, # J/K/mol
"T": 273.15 + 25., # K
}

The parameters and the mesh are passed to the initiator of parent class
:py:class:`EchemSolver <echemfem.EchemSolver>`. The optional argument ``family``
sets the finite element space; here, ``"CG"`` stands for continuous Galerkin,
which is usually the fastest option, whereas ``"DG"`` stands for discontinuous Galerkin, which can provide more stability for advection-dominated cases::

super().__init__(conc_params, physical_params, mesh, family="CG",
echem_params=echem_params,
homog_params=homog_params)

The boundary conditions are set through this abstract method, which the user always needs to specify. Note that the name ``"electrode"`` only has meaning because it is provided in ``echem_params``. Also note that the "natural" boundary condition for this finite element formulation is no-flux, so it is the default boundary condition::

def set_boundary_markers(self):
self.boundary_markers = {"inlet": (1,),
"outlet": (2,),
"bulk": (4,),
"electrode": (3,),
}

Similarly, the velocity field is set through an abstract method::

def set_velocity(self):
_, y = SpatialCoordinate(self.mesh)
self.vel = as_vector([1.91*y, Constant(0)]) # m/s

Finally, we create our solver object, set up the solver, and run it::

solver = GuptaSolver()
solver.setup_solver()
solver.solve()

The solution fields can be visualized by opening ``results/collection.pvd``
using Paraview. For example, the :math:`\ce{CO2}` solution field:

.. image:: CO2_solution_field.png
:width: 600px
:align: center

This demo can be found as a script :download:`here <flow_reactor.py>`

.. rubric:: References

.. bibliography:: demo_references.bib
:filter: docname in docnames
11 changes: 11 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ help:

.PHONY: help Makefile

.PHONY: copy_demos

source/demos: copy_demos

copy_demos:
install -d source/demos
cp ../demos/*/*.rst ../demos/*/*.png ../demos/demo_references.bib source/demos
for file in source/demos/*.py.rst; do pylit $$file; done

html: Makefile copy_demos
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
Expand Down
37 changes: 37 additions & 0 deletions docs/source/_static/references.bib
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@article{Taylor1973,
title={A numerical solution of the {N}avier-{S}tokes equations using the finite element technique},
author={Taylor, Cedric and Hood, Paul},
journal={Computers \& Fluids},
volume={1},
number={1},
pages={73--100},
year={1973},
publisher={Elsevier}
}

@misc{Ladyzhenskaya1963,
title={The mathematical theory of incompressible viscous flows},
author={Ladyzhenskaya, O},
year={1963},
publisher={Gordon and Breach, New York}
}

@article{Brezzi1974,
title={On the existence, uniqueness and approximation of saddle-point problems arising from {L}agrangian multipliers},
author={Brezzi, Franco},
journal={Publications des s{\'e}minaires de math{\'e}matiques et informatique de Rennes},
number={S4},
pages={1--26},
year={1974}
}

@article{Babuvska1971,
title={Error-bounds for finite element method},
author={Babu{\v{s}}ka, Ivo},
journal={Numerische Mathematik},
volume={16},
number={4},
pages={322--333},
year={1971},
publisher={Springer}
}
4 changes: 4 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
'sphinx.ext.intersphinx',
'sphinx.ext.napoleon',
'sphinx.ext.mathjax',
'sphinxcontrib.bibtex',
]

mathjax3_config = {
Expand All @@ -36,6 +37,9 @@

templates_path = ['_templates']

# -- Options for sphinxcontrib.bibtex ------------------------------------
bibtex_bibfiles = ['demos/demo_references.bib', '_static/references.bib']

# -- Options for HTML output

html_theme = 'sphinx_rtd_theme'
Expand Down
Loading

0 comments on commit 39080b3

Please sign in to comment.