examples.levelSet.electroChem.howToWriteAScript¶
Tutorial for writing an electrochemical superfill script.
This input file demonstrates how to
create a new superfill script if the existing suite of scripts do
not meet the required needs. It provides the functionality of
examples.levelSet.electroChem.simpleTrenchSystem
.
To run this example from the base FiPy directory type:
$ python examples/levelSet/electroChem/howToWriteAScript.py --numberOfElements=10000 --numberOfSteps=800
at the command line. The results of the simulation will be displayed
and the word finished
in the terminal at the end of the
simulation. To obtain this example in a plain script file in order to
edit and run type:
$ python setup.py copy_script --From examples/levelSet/electroChem/howToWriteAScript.py --To myScript.py
in the base FiPy directory. The file myScript.py
will contain the
script.
The following is an explicit explanation of the input commands
required to set up and run the problem. At the top of the file all the
parameter values are set. Their use will be explained during the
instantiation of various objects and are the same as those explained in
examples.levelSet.electroChem.simpleTrenchSystem
.
The following parameters (all in S.I. units) represent,
physical constants,
>>> faradaysConstant = 9.6e4 >>> gasConstant = 8.314 >>> transferCoefficient = 0.5properties associated with the catalyst species,
>>> rateConstant0 = 1.76 >>> rateConstant3 = -245e-6 >>> catalystDiffusion = 1e-9 >>> siteDensity = 9.8e-6properties of the cupric ions,
>>> molarVolume = 7.1e-6 >>> charge = 2 >>> metalDiffusionCoefficient = 5.6e-10parameters dependent on experimental constraints,
>>> temperature = 298. >>> overpotential = -0.3 >>> bulkMetalConcentration = 250. >>> catalystConcentration = 5e-3 >>> catalystCoverage = 0.parameters obtained from experiments on flat copper electrodes,
>>> currentDensity0 = 0.26 >>> currentDensity1 = 45.general simulation control parameters,
>>> cflNumber = 0.2 >>> numberOfCellsInNarrowBand = 10 >>> cellsBelowTrench = 10 >>> cellSize = 0.1e-7parameters required for a trench geometry,
>>> trenchDepth = 0.5e-6 >>> aspectRatio = 2. >>> trenchSpacing = 0.6e-6 >>> boundaryLayerDepth = 0.3e-6
The hydrodynamic boundary layer depth (boundaryLayerDepth
) is
intentionally small in this example to keep the mesh at a reasonable
size.
Build the mesh:
>>> from fipy.tools.parser import parse
>>> numberOfElements = parse('--numberOfElements', action='store',
... type='int', default=-1)
>>> numberOfSteps = parse('--numberOfSteps', action='store',
... type='int', default=2)
>>> from fipy import *
>>> if numberOfElements != -1:
... pos = trenchSpacing * cellsBelowTrench / 4 / numberOfElements
... sqr = trenchSpacing * (trenchDepth + boundaryLayerDepth) \
... / (2 * numberOfElements)
... cellSize = pos + numerix.sqrt(pos**2 + sqr)
... else:
... cellSize = 0.1e-7
>>> yCells = cellsBelowTrench \
... + int((trenchDepth + boundaryLayerDepth) / cellSize)
>>> xCells = int(trenchSpacing / 2 / cellSize)
>>> from .metalIonDiffusionEquation import buildMetalIonDiffusionEquation
>>> from .adsorbingSurfactantEquation import AdsorbingSurfactantEquation
>>> from fipy import serialComm
>>> mesh = Grid2D(dx=cellSize,
... dy=cellSize,
... nx=xCells,
... ny=yCells,
... communicator=serialComm)
A distanceVariable
object,
, is required to store the position of the interface.
The distanceVariable
calculates its value so that it is a distance
function
(i.e. holds the distance at any point in the mesh from the electrolyte/metal
interface at ) and
.
First, create the variable, which is initially set to -1 everywhere.
Create an initial variable,
>>> narrowBandWidth = numberOfCellsInNarrowBand * cellSize
>>> distanceVar = DistanceVariable(
... name='distance variable',
... mesh= mesh,
... value=-1.,
... hasOld=1)
The electrolyte region will be the positive region of the domain while the metal region will be negative.
>>> bottomHeight = cellsBelowTrench * cellSize
>>> trenchHeight = bottomHeight + trenchDepth
>>> trenchWidth = trenchDepth / aspectRatio
>>> sideWidth = (trenchSpacing - trenchWidth) / 2
>>> x, y = mesh.cellCenters
>>> distanceVar.setValue(1., where=(y > trenchHeight)
... | ((y > bottomHeight)
... & (x < xCells * cellSize - sideWidth)))
>>> distanceVar.calcDistanceFunction(order=2)
The distanceVariable
has now been created to mark the interface. Some other
variables need to be created that govern the concentrations of various species.
Create the catalyst surfactant coverage, , variable.
This variable influences the deposition rate.
>>> catalystVar = SurfactantVariable(
... name="catalyst variable",
... value=catalystCoverage,
... distanceVar=distanceVar)
Create the bulk catalyst concentration, ,
in the electrolyte,
>>> bulkCatalystVar = CellVariable(
... name='bulk catalyst variable',
... mesh=mesh,
... value=catalystConcentration)
Create the bulk metal ion concentration,
, in the electrolyte.
>>> metalVar = CellVariable(
... name='metal variable',
... mesh=mesh,
... value=bulkMetalConcentration)
The following commands build the depositionRateVariable
,
. The
depositionRateVariable
is given by the following equation.
where is the metal molar volume,
is the metal ion
charge and
is Faraday’s constant. The current density is given
by
where is the metal ion concentration in the bulk at the
interface,
is the far-field bulk concentration of
metal ions,
is the transfer coefficient,
is the gas
constant,
is the temperature and
is the
overpotential. The exchange current density is an empirical
function of catalyst coverage,
The commands needed to build this equation are,
>>> expoConstant = -transferCoefficient * faradaysConstant \
... / (gasConstant * temperature)
>>> tmp = currentDensity1 \
... * catalystVar.interfaceVar
>>> exchangeCurrentDensity = currentDensity0 + tmp
>>> expo = numerix.exp(expoConstant * overpotential)
>>> currentDensity = expo * exchangeCurrentDensity * metalVar \
... / bulkMetalConcentration
>>> depositionRateVariable = currentDensity * molarVolume \
... / (charge * faradaysConstant)
Build the extension velocity variable . The extension
velocity uses the
extensionEquation
to spread the velocity at the interface to the
rest of the domain.
>>> extensionVelocityVariable = CellVariable(
... name='extension velocity',
... mesh=mesh,
... value=depositionRateVariable)
Using the variables created above the governing equations will be built. The governing equation for surfactant conservation is given by,
where is the coverage of catalyst at the interface,
is the curvature of the interface,
is the normal velocity
of the interface,
is the concentration of
catalyst in the bulk at the interface. The value
is given
by an empirical function of overpotential,
The above equation is represented by the
AdsorbingSurfactantEquation
in FiPy:
>>> surfactantEquation = AdsorbingSurfactantEquation(
... surfactantVar=catalystVar,
... distanceVar=distanceVar,
... bulkVar=bulkCatalystVar,
... rateConstant=rateConstant0 \
... + rateConstant3 * overpotential**3)
The variable is advected by the
advectionEquation
given by,
and is set up with the following commands:
>>> advectionEquation = TransientTerm() + AdvectionTerm(extensionVelocityVariable)
The diffusion of metal ions from the far field to the interface is governed by,
where,
The following boundary condition applies at ,
The metal ion diffusion equation is set up with the following commands.
>>> metalEquation = buildMetalIonDiffusionEquation(
... ionVar=metalVar,
... distanceVar=distanceVar,
... depositionRate=depositionRateVariable,
... diffusionCoeff=metalDiffusionCoefficient,
... metalIonMolarVolume=molarVolume,
... )
>>> metalVar.constrain(bulkMetalConcentration, mesh.facesTop)
The surfactant bulk diffusion equation solves the bulk diffusion of a species with a source term for the jump from the bulk to an interface. The governing equation is given by,
where,
The jump condition at the interface is defined by Langmuir
adsorption. Langmuir adsorption essentially states that the ability
for a species to jump from an electrolyte to an interface is
proportional to the concentration in the electrolyte, available site
density and a jump coefficient. The boundary condition
at is given by,
The surfactant bulk diffusion equation is set up with the following commands.
>>> from .surfactantBulkDiffusionEquation import buildSurfactantBulkDiffusionEquation
>>> bulkCatalystEquation = buildSurfactantBulkDiffusionEquation(
... bulkVar=bulkCatalystVar,
... distanceVar=distanceVar,
... surfactantVar=catalystVar,
... diffusionCoeff=catalystDiffusion,
... rateConstant=rateConstant0 * siteDensity
... )
>>> bulkCatalystVar.constrain(catalystConcentration, mesh.facesTop)
If running interactively, create viewers.
>>> if __name__ == '__main__':
... try:
... from .mayaviSurfactantViewer import MayaviSurfactantViewer
... viewer = MayaviSurfactantViewer(distanceVar,
... catalystVar.interfaceVar,
... zoomFactor=1e6,
... datamax=1.0,
... datamin=0.0,
... smooth=1)
... except:
... viewer = MultiViewer(viewers=(
... Viewer(distanceVar, datamin=-1e-9, datamax=1e-9),
... Viewer(catalystVar.interfaceVar)))
... from fipy.models.levelSet.surfactant.matplotlibSurfactantViewer import MatplotlibSurfactantViewer
... viewer = MatplotlibSurfactantViewer(catalystVar.interfaceVar)
... else:
... viewer = None
The levelSetUpdateFrequency
defines how often to call the
distanceEquation
to reinitialize the distanceVariable
to a
distance function.
>>> levelSetUpdateFrequency = int(0.8 * narrowBandWidth \
... / (cellSize * cflNumber * 2))
The following loop runs for numberOfSteps
time steps. The time step
is calculated with the CFL number and the maximum extension velocity.
to
throughout the whole domain using
.
>>> from builtins import range
>>> for step in range(numberOfSteps):
...
... if viewer is not None:
... viewer.plot()
...
... if step % levelSetUpdateFrequency == 0:
... distanceVar.calcDistanceFunction(order=2)
...
... extensionVelocityVariable.setValue(depositionRateVariable())
...
... distanceVar.updateOld()
... distanceVar.extendVariable(extensionVelocityVariable, order=2)
... dt = cflNumber * cellSize / extensionVelocityVariable.max()
... advectionEquation.solve(distanceVar, dt=dt)
... surfactantEquation.solve(catalystVar, dt=dt)
... metalEquation.solve(var=metalVar, dt=dt)
... bulkCatalystEquation.solve(var=bulkCatalystVar, dt=dt, solver=GeneralSolver())
The following is a short test case. It uses saved data from a simulation with 5 time steps. It is not a test for accuracy but a way to tell if something has changed or been broken.
>>> import os
>>> filepath = os.path.join(os.path.split(__file__)[0],
... "simpleTrenchSystem.gz")
>>> ##numerix.savetxt(filepath, numerix.array(catalystVar))
>>> print(catalystVar.allclose(numerix.loadtxt(filepath), rtol=1e-4))
1
>>> from fipy import input
>>> if __name__ == '__main__':
... input('finished')