examples.elphf.input¶
This example adds two more components to
examples/elphf/input1DphaseBinary.py
one of which is another substitutional species and the other represents
electrons and diffuses interstitially.
Parameters from 2004/January/21/elphf0214
We start by defining a 1D mesh
>>> from fipy import PhysicalField as PF
>>> RT = (PF("1 Nav*kB") * PF("298 K"))
>>> molarVolume = PF("1.80000006366754e-05 m**3/mol")
>>> Faraday = PF("1 Nav*e")
>>> L = PF("3 nm")
>>> nx = 1200
>>> dx = L / nx
>>> # nx = 200
>>> # dx = PF("0.01 nm")
>>> ## dx = PF("0.001 nm") * (1.001 - 1/cosh(arange(-10, 10, .01)))
>>> # L = nx * dx
>>> mesh = Grid1D(dx = dx, nx = nx)
>>> # mesh = Grid1D(dx = dx)
>>> # L = mesh.facesRight[0].center[0] - mesh.facesLeft[0].center[0]
>>> # L = mesh.cellCenters[0,-1] - mesh.cellCenters[0,0]
We create the phase field
>>> timeStep = PF("1e-12 s")
>>> phase = CellVariable(mesh = mesh, name = 'xi', value = 1, hasOld = 1)
>>> phase.mobility = PF("1 m**3/J/s") / (molarVolume / (RT * timeStep))
>>> phase.gradientEnergy = PF("3.6e-11 J/m") / (mesh.scale**2 * RT / molarVolume)
>>> def p(xi):
... return xi**3 * (6 * xi**2 - 15 * xi + 10.)
>>> def g(xi):
... return (xi * (1 - xi))**2
>>> def pPrime(xi):
... return 30. * (xi * (1 - xi))**2
>>> def gPrime(xi):
... return 4 * xi * (1 - xi) * (0.5 - xi)
We create four components
>>> class ComponentVariable(CellVariable):
... def __init__(self, mesh, value = 0., name = '', standardPotential = 0., barrier = 0., diffusivity = None, valence = 0, equation = None, hasOld = 1):
... self.standardPotential = standardPotential
... self.barrier = barrier
... self.diffusivity = diffusivity
... self.valence = valence
... self.equation = equation
... CellVariable.__init__(self, mesh = mesh, value = value, name = name, hasOld = hasOld)
...
... def copy(self):
... return self.__class__(mesh = self.mesh, value = self.value,
... name = self.name,
... standardPotential = self.standardPotential,
... barrier = self.barrier,
... diffusivity = self.diffusivity,
... valence = self.valence,
... equation = self.equation,
... hasOld = 0)
the solvent
>>> solvent = ComponentVariable(mesh = mesh, name = 'H2O', value = 1.)
>>> CnStandardPotential = PF("34139.7265625 J/mol") / RT
>>> CnBarrier = PF("3.6e5 J/mol") / RT
>>> CnValence = 0
and two solute species
>>> substitutionals = [
... ComponentVariable(mesh = mesh, name = 'SO4',
... diffusivity = PF("1e-9 m**2/s") / (mesh.scale**2/timeStep),
... standardPotential = PF("24276.6640625 J/mol") / RT,
... barrier = CnBarrier,
... valence = -2,
... value = PF("0.000010414586295976 mol/l") * molarVolume),
... ComponentVariable(mesh = mesh, name = 'Cu',
... diffusivity = PF("1e-9 m**2/s") / (mesh.scale**2/timeStep),
... standardPotential = PF("-7231.81396484375 J/mol") / RT,
... barrier = CnBarrier,
... valence = +2,
... value = PF("55.5553718417909 mol/l") * molarVolume)]
and one interstitial
>>> interstitials = [
... ComponentVariable(mesh = mesh, name = 'e-',
... diffusivity = PF("1e-9 m**2/s") / (mesh.scale**2/timeStep),
... standardPotential = PF("-33225.9453125 J/mol") / RT,
... barrier = 0.,
... valence = -1,
... value = PF("111.110723815414 mol/l") * molarVolume)]
>>> for component in substitutionals:
... solvent -= component
... component.standardPotential -= CnStandardPotential
... component.barrier -= CnBarrier
... component.valence -= CnValence
Finally, we create the electrostatic potential field
>>> potential = CellVariable(mesh = mesh, name = 'phi', value = 0.)
>>> permittivity = PF("78.49 eps0") / (Faraday**2 * mesh.scale**2 / (RT * molarVolume))
>>> permittivity = 1.
>>> permittivityPrime = 0.
The thermodynamic parameters are chosen to give a solid phase rich in electrons and the solvent and a liquid phase rich in the two substitutional species
>>> solvent.standardPotential = CnStandardPotential
>>> solvent.barrier = CnBarrier
>>> solvent.valence = CnValence
Once again, we start with a sharp phase boundary
>>> x = mesh.cellCenters[0]
>>> phase.setValue(x < L / 2)
>>> interstitials[0].setValue("0.000111111503177394 mol/l" * molarVolume, where=x > L / 2)
>>> substitutionals[0].setValue("0.249944439430068 mol/l" * molarVolume, where=x > L / 2)
>>> substitutionals[1].setValue("0.249999982581341 mol/l" * molarVolume, where=x > L / 2)
We again create the phase equation as in examples.elphf.phase.input1D
>>> mesh.setScale(1)
>>> phase.equation = TransientTerm(coeff = 1/phase.mobility) \
... == DiffusionTerm(coeff = phase.gradientEnergy) \
... - (permittivityPrime / 2.) * potential.grad.dot(potential.grad)
We linearize the source term in the same way as in example.phase.simple.input1D.
>>> enthalpy = solvent.standardPotential
>>> barrier = solvent.barrier
>>> for component in substitutionals + interstitials:
... enthalpy += component * component.standardPotential
... barrier += component * component.barrier
>>> mXi = -(30 * phase * (1 - phase) * enthalpy + 4 * (0.5 - phase) * barrier)
>>> dmXidXi = (-60 * (0.5 - phase) * enthalpy + 4 * barrier)
>>> S1 = dmXidXi * phase * (1 - phase) + mXi * (1 - 2 * phase)
>>> S0 = mXi * phase * (1 - phase) - phase * S1
>>> phase.equation -= S0 + ImplicitSourceTerm(coeff = S1)
and we create the diffusion equation for the solute as in
examples.elphf.diffusion.input1D
>>> for Cj in substitutionals:
... CkSum = ComponentVariable(mesh = mesh, value = 0.)
... CkFaceSum = FaceVariable(mesh = mesh, value = 0.)
... for Ck in [Ck for Ck in substitutionals if Ck is not Cj]:
... CkSum += Ck
... CkFaceSum += Ck.harmonicFaceValue
...
... counterDiffusion = CkSum.faceGrad
... # phaseTransformation = (pPrime(phase.harmonicFaceValue) * Cj.standardPotential
... # + gPrime(phase.harmonicFaceValue) * Cj.barrier) * phase.faceGrad
... phaseTransformation = (pPrime(phase).harmonicFaceValue * Cj.standardPotential
... + gPrime(phase).harmonicFaceValue * Cj.barrier) * phase.faceGrad
... # phaseTransformation = (p(phase).faceGrad * Cj.standardPotential
... # + g(phase).faceGrad * Cj.barrier)
... electromigration = Cj.valence * potential.faceGrad
... convectionCoeff = counterDiffusion + \
... solvent.harmonicFaceValue * (phaseTransformation + electromigration)
... convectionCoeff *= (Cj.diffusivity / (1. - CkFaceSum))
...
... Cj.equation = (TransientTerm()
... == DiffusionTerm(coeff=Cj.diffusivity)
... + PowerLawConvectionTerm(coeff=convectionCoeff))
>>> for Cj in interstitials:
... # phaseTransformation = (pPrime(phase.harmonicFaceValue) * Cj.standardPotential
... # + gPrime(phase.harmonicFaceValue) * Cj.barrier) * phase.faceGrad
... phaseTransformation = (pPrime(phase).harmonicFaceValue * Cj.standardPotential
... + gPrime(phase).harmonicFaceValue * Cj.barrier) * phase.faceGrad
... # phaseTransformation = (p(phase).faceGrad * Cj.standardPotential
... # + g(phase).faceGrad * Cj.barrier)
... electromigration = Cj.valence * potential.faceGrad
... convectionCoeff = Cj.diffusivity * (1 + Cj.harmonicFaceValue) * \
... (phaseTransformation + electromigration)
...
... Cj.equation = (TransientTerm()
... == DiffusionTerm(coeff=Cj.diffusivity)
... + PowerLawConvectionTerm(coeff=convectionCoeff))
And Poisson’s equation
>>> charge = 0.
>>> for Cj in interstitials + substitutionals:
... charge += Cj * Cj.valence
>>> potential.equation = DiffusionTerm(coeff = permittivity) + charge == 0
If running interactively, we create viewers to display the results
>>> from fipy import input
>>> if __name__ == '__main__':
... phaseViewer = Viewer(vars=phase, datamin=0, datamax=1)
... concViewer = Viewer(vars=[solvent] + substitutionals + interstitials, ylog=True)
... potentialViewer = Viewer(vars = potential)
... phaseViewer.plot()
... concViewer.plot()
... potentialViewer.plot()
... input("Press a key to continue")
Again, this problem does not have an analytical solution, so after iterating to equilibrium
>>> solver = LinearLUSolver(tolerance = 1e-3)
>>> potential.constrain(0., mesh.facesLeft)
>>> phase.residual = CellVariable(mesh = mesh)
>>> potential.residual = CellVariable(mesh = mesh)
>>> for Cj in substitutionals + interstitials:
... Cj.residual = CellVariable(mesh = mesh)
>>> residualViewer = Viewer(vars = [phase.residual, potential.residual] + [Cj.residual for Cj in substitutionals + interstitials])
>>> tsv = TSVViewer(vars = [phase, potential] + substitutionals + interstitials)
>>> dt = substitutionals[0].diffusivity * 100
>>> # dt = 1.
>>> elapsed = 0.
>>> maxError = 1e-1
>>> SAFETY = 0.9
>>> ERRCON = 1.89e-4
>>> desiredTimestep = 1.
>>> thisTimeStep = 0.
>>> print("%3s: %20s | %20s | %20s | %20s" % ("i", "elapsed", "this", "next dt", "residual"))
>>> residual = 0.
>>> from builtins import range
>>> from builtins import str
>>> for i in range(500): # iterate
... if thisTimeStep == 0.:
... tsv.plot(filename = "%s.tsv" % str(elapsed * timeStep))
...
... for field in [phase, potential] + substitutionals + interstitials:
... field.updateOld()
...
... while True:
... for j in range(10): # sweep
... print(i, j, dt * timeStep, residual)
... # raw_input()
... residual = 0.
...
... phase.equation.solve(var = phase, dt = dt)
... # print phase.name, phase.equation.residual.max()
... residual = max(phase.equation.residual.max(), residual)
... phase.residual[:] = phase.equation.residual
...
... potential.equation.solve(var = potential, dt = dt)
... # print potential.name, potential.equation.residual.max()
... residual = max(potential.equation.residual.max(), residual)
... potential.residual[:] = potential.equation.residual
...
... for Cj in substitutionals + interstitials:
... Cj.equation.solve(var = Cj,
... dt = dt,
... solver = solver)
... # print Cj.name, Cj.equation.residual.max()
... residual = max(Cj.equation.residual.max(), residual)
... Cj.residual[:] = Cj.equation.residual
...
... # print
... # phaseViewer.plot()
... # concViewer.plot()
... # potentialViewer.plot()
... # residualViewer.plot()
...
... residual /= maxError
... if residual <= 1.:
... break # step succeeded
...
... dt = max(SAFETY * dt * residual**-0.2, 0.1 * dt)
... if thisTimeStep + dt == thisTimeStep:
... raise FloatingPointError("step size underflow")
...
... thisTimeStep += dt
...
... if residual > ERRCON:
... dt *= SAFETY * residual**-0.2
... else:
... dt *= 5.
...
... # dt *= (maxError / residual)**0.5
...
... if thisTimeStep >= desiredTimestep:
... elapsed += thisTimeStep
... thisTimeStep = 0.
... else:
... dt = min(dt, desiredTimestep - thisTimeStep)
...
... if __name__ == '__main__':
... phaseViewer.plot()
... concViewer.plot()
... potentialViewer.plot()
... print("%3d: %20s | %20s | %20s | %g" % (i, str(elapsed * timeStep), str(thisTimeStep * timeStep), str(dt * timeStep), residual))
we confirm that the far-field phases have remained separated
>>> ends = take(phase, (0, -1))
>>> allclose(ends, (1.0, 0.0), rtol = 1e-5, atol = 1e-5)
1
and that the concentration fields has appropriately segregated into into their respective phases
>>> ends = take(interstitials[0], (0, -1))
>>> allclose(ends, (0.4, 0.3), rtol = 3e-3, atol = 3e-3)
1
>>> ends = take(substitutionals[0], (0, -1))
>>> allclose(ends, (0.3, 0.4), rtol = 3e-3, atol = 3e-3)
1
>>> ends = take(substitutionals[1], (0, -1))
>>> allclose(ends, (0.1, 0.2), rtol = 3e-3, atol = 3e-3)
1