examples.cahnHilliard package¶
Submodules¶
examples.cahnHilliard.mesh2D module¶
The spinodal decomposition phenomenon is a spontaneous separation of an initially homogeneous mixture into two distinct regions of different properties (spin-up/spin-down, component A/component B). It is a “barrierless” phase separation process, such that under the right thermodynamic conditions, any fluctuation, no matter how small, will tend to grow. This is in contrast to nucleation, where a fluctuation must exceed some critical magnitude before it will survive and grow. Spinodal decomposition can be described by the “Cahn-Hilliard” equation (also known as “conserved Ginsberg-Landau” or “model B” of Hohenberg & Halperin)
where is a conserved order parameter, possibly representing
alloy composition or spin.
The double-well free energy function
penalizes states with intermediate values of
between 0 and 1. The gradient energy term
,
on the other hand, penalizes sharp changes of
.
These two competing effects result in the segregation
of
into domains of 0 and 1, separated by abrupt, but
smooth, transitions. The parameters
and
determine the relative
weighting of the two effects and
is a rate constant.
We can simulate this process in FiPy with a simple script:
>>> from fipy import CellVariable, Grid2D, GaussianNoiseVariable, TransientTerm, DiffusionTerm, ImplicitSourceTerm, LinearLUSolver, Viewer
>>> from fipy.tools import numerix
(Note that all of the functionality of NumPy is imported along with FiPy, although much is augmented for FiPy's needs.)
>>> if __name__ == "__main__":
... nx = ny = 1000
... else:
... nx = ny = 20
>>> mesh = Grid2D(nx=nx, ny=ny, dx=0.25, dy=0.25)
>>> phi = CellVariable(name=r"$\phi$", mesh=mesh)
We start the problem with random fluctuations about
>>> phi.setValue(GaussianNoiseVariable(mesh=mesh,
... mean=0.5,
... variance=0.01))
FiPy doesn’t plot or output anything unless you tell it to:
>>> if __name__ == "__main__":
... viewer = Viewer(vars=(phi,), datamin=0., datamax=1.)
For FiPy, we need to perform the partial derivative
manually and then put the equation in the canonical
form by decomposing the spatial derivatives
so that each
Term
is of a single, even order:
FiPy would automatically interpolate
D * a**2 * (1 - 6 * phi * (1 - phi))
onto the faces, where the diffusive flux is calculated, but we obtain
somewhat more accurate results by performing a linear interpolation from
phi
at cell centers to PHI
at face centers.
Some problems benefit from non-linear interpolations, such as harmonic or
geometric means, and FiPy makes it easy to obtain these, too.
>>> PHI = phi.arithmeticFaceValue
>>> D = a = epsilon = 1.
>>> eq = (TransientTerm()
... == DiffusionTerm(coeff=D * a**2 * (1 - 6 * PHI * (1 - PHI)))
... - DiffusionTerm(coeff=(D, epsilon**2)))
Because the evolution of a spinodal microstructure slows with time, we use exponentially increasing time steps to keep the simulation “interesting”. The FiPy user always has direct control over the evolution of their problem.
>>> dexp = -5
>>> elapsed = 0.
>>> if __name__ == "__main__":
... duration = 1000.
... else:
... duration = 1000.
>>> while elapsed < duration:
... dt = min(100, numerix.exp(dexp))
... elapsed += dt
... dexp += 0.01
... eq.solve(phi, dt=dt, solver=LinearLUSolver())
... if __name__ == "__main__":
... viewer.plot()
... elif (max(phi.globalValue) > 0.7) and (min(phi.globalValue) < 0.3) and elapsed > 10.:
... break
>>> print((max(phi.globalValue) > 0.7) and (min(phi.globalValue) < 0.3))
True
examples.cahnHilliard.mesh2DCoupled module¶
Solve the Cahn-Hilliard problem in two dimensions.
Warning
This formulation has serious performance problems and is not automatically tested. Specifically, for non-trivial mesh sizes, PySparse requires enormous amounts of memory, Trilinos cannot solve the coupled form, and PETSc cannot solve the vector form.
The spinodal decomposition phenomenon is a spontaneous separation of an initially homogeneous mixture into two distinct regions of different properties (spin-up/spin-down, component A/component B). It is a “barrierless” phase separation process, such that under the right thermodynamic conditions, any fluctuation, no matter how small, will tend to grow. This is in contrast to nucleation, where a fluctuation must exceed some critical magnitude before it will survive and grow. Spinodal decomposition can be described by the “Cahn-Hilliard” equation (also known as “conserved Ginsberg-Landau” or “model B” of Hohenberg & Halperin)
where is a conserved order parameter, possibly representing
alloy composition or spin.
The double-well free energy function
penalizes states with intermediate values of
between 0 and 1. The gradient energy term
,
on the other hand, penalizes sharp changes of
.
These two competing effects result in the segregation
of
into domains of 0 and 1, separated by abrupt, but
smooth, transitions. The parameters
and
determine the relative
weighting of the two effects and
is a rate constant.
We can simulate this process in FiPy with a simple script:
>>> from fipy import CellVariable, Grid2D, GaussianNoiseVariable, DiffusionTerm, TransientTerm, ImplicitSourceTerm, Viewer
>>> from fipy.tools import numerix
(Note that all of the functionality of NumPy is imported along with FiPy, although much is augmented for FiPy's needs.)
>>> if __name__ == "__main__":
... nx = ny = 20
... else:
... nx = ny = 10
>>> mesh = Grid2D(nx=nx, ny=ny, dx=0.25, dy=0.25)
>>> phi = CellVariable(name=r"$\phi$", mesh=mesh)
>>> psi = CellVariable(name=r"$\psi$", mesh=mesh)
We start the problem with random fluctuations about
>>> noise = GaussianNoiseVariable(mesh=mesh,
... mean=0.5,
... variance=0.01).value
>>> phi[:] = noise
FiPy doesn’t plot or output anything unless you tell it to:
>>> if __name__ == "__main__":
... viewer = Viewer(vars=(phi,), datamin=0., datamax=1.)
We factor the Cahn-Hilliard equation into two 2nd-order PDEs and place them in canonical form for FiPy to solve them as a coupled set of equations.
The source term in ,
, is
expressed in linearized form after Taylor expansion at
,
for the same reasons discussed in
examples.phase.simple
.
We need to perform the partial derivatives
manually.
>>> D = a = epsilon = 1.
>>> dfdphi = a**2 * phi * (1 - phi) * (1 - 2 * phi)
>>> dfdphi_ = a**2 * (1 - phi) * (1 - 2 * phi)
>>> d2fdphi2 = a**2 * (1 - 6 * phi * (1 - phi))
>>> eq1 = (TransientTerm(var=phi) == DiffusionTerm(coeff=D, var=psi))
>>> eq2 = (ImplicitSourceTerm(coeff=1., var=psi)
... == ImplicitSourceTerm(coeff=d2fdphi2, var=phi) - d2fdphi2 * phi + dfdphi
... - DiffusionTerm(coeff=epsilon**2, var=phi))
>>> eq3 = (ImplicitSourceTerm(coeff=1., var=psi)
... == ImplicitSourceTerm(coeff=dfdphi_, var=phi)
... - DiffusionTerm(coeff=epsilon**2, var=phi))
>>> eq = eq1 & eq2
Because the evolution of a spinodal microstructure slows with time, we use exponentially increasing time steps to keep the simulation “interesting”. The FiPy user always has direct control over the evolution of their problem.
>>> dexp = -5
>>> elapsed = 0.
>>> if __name__ == "__main__":
... duration = 100.
... else:
... duration = .5e-1
>>> while elapsed < duration:
... dt = min(100, numerix.exp(dexp))
... elapsed += dt
... dexp += 0.01
... eq.solve(dt=dt)
... if __name__ == "__main__":
... viewer.plot()
>>> from fipy import input
>>> if __name__ == '__main__':
... input("Coupled equations. Press <return> to proceed...")
These equations can also be solved in FiPy using a vector
equation. The variables and
are now stored in
a single variable
>>> var = CellVariable(mesh=mesh, elementshape=(2,))
>>> var[0] = noise
>>> if __name__ == "__main__":
... viewer = Viewer(name=r"$\phi$", vars=var[0,], datamin=0., datamax=1.)
>>> D = a = epsilon = 1.
>>> v0 = var[0]
>>> dfdphi = a**2 * v0 * (1 - v0) * (1 - 2 * v0)
>>> dfdphi_ = a**2 * (1 - v0) * (1 - 2 * v0)
>>> d2fdphi2 = a**2 * (1 - 6 * v0 * (1 - v0))
The source terms have to be shaped correctly for a vector. The implicit source coefficient has to have a shape of (2, 2) while the explicit source has a shape (2,)
>>> source = (- d2fdphi2 * v0 + dfdphi) * (0, 1)
>>> impCoeff = d2fdphi2 * ((0, 0),
... (1., 0)) + ((0, 0),
... (0, -1.))
This is the same equation as the previous definition of eq, but now in a vector format.
>>> eq = (TransientTerm(((1., 0.),
... (0., 0.))) == DiffusionTerm([((0., D),
... (-epsilon**2, 0.))])
... + ImplicitSourceTerm(impCoeff) + source)
>>> dexp = -5
>>> elapsed = 0.
>>> while elapsed < duration:
... dt = min(100, numerix.exp(dexp))
... elapsed += dt
... dexp += 0.01
... eq.solve(var=var, dt=dt)
... if __name__ == "__main__":
... viewer.plot()
>>> print(numerix.allclose(var, (phi, psi)))
True
examples.cahnHilliard.mesh3D module¶
Solves the Cahn-Hilliard problem in a 3D cube
>>> from fipy import CellVariable, Grid3D, Viewer, GaussianNoiseVariable, TransientTerm, DiffusionTerm, DefaultSolver
>>> from fipy.tools import numerix
The only difference from examples.cahnHilliard.mesh2D
is the
declaration of mesh
.
>>> if __name__ == "__main__":
... nx = ny = nz = 100
... else:
... nx = ny = nz = 10
>>> mesh = Grid3D(nx=nx, ny=ny, nz=nz, dx=0.25, dy=0.25, dz=0.25)
>>> phi = CellVariable(name=r"$\phi$", mesh=mesh)
We start the problem with random fluctuations about
>>> phi.setValue(GaussianNoiseVariable(mesh=mesh,
... mean=0.5,
... variance=0.01))
FiPy doesn’t plot or output anything unless you tell it to:
>>> if __name__ == "__main__":
... viewer = Viewer(vars=(phi,), datamin=0., datamax=1.)
For FiPy, we need to perform the partial derivative
manually and then put the equation in the canonical
form by decomposing the spatial derivatives
so that each
Term
is of a single, even order:
FiPy would automatically interpolate
D * a**2 * (1 - 6 * phi * (1 - phi))
onto the Face
s, where the diffusive flux is calculated, but we obtain
somewhat more accurate results by performing a linear interpolation from
phi
at Cell
centers to PHI
at Face
centers.
Some problems benefit from non-linear interpolations, such as harmonic or
geometric means, and FiPy makes it easy to obtain these, too.
>>> PHI = phi.arithmeticFaceValue
>>> D = a = epsilon = 1.
>>> eq = (TransientTerm()
... == DiffusionTerm(coeff=D * a**2 * (1 - 6 * PHI * (1 - PHI)))
... - DiffusionTerm(coeff=(D, epsilon**2)))
Because the evolution of a spinodal microstructure slows with time, we use exponentially increasing time steps to keep the simulation “interesting”. The FiPy user always has direct control over the evolution of their problem.
>>> dexp = -5
>>> elapsed = 0.
>>> if __name__ == "__main__":
... duration = 1000.
... else:
... duration = 1e-2
>>> while elapsed < duration:
... dt = min(100, numerix.exp(dexp))
... elapsed += dt
... dexp += 0.01
... eq.solve(phi, dt=dt, solver=DefaultSolver(precon=None))
... if __name__ == "__main__":
... viewer.plot()
examples.cahnHilliard.sphere module¶
Solves the Cahn-Hilliard problem on the surface of a sphere.
This phenomenon can occur on vesicles (http://www.youtube.com/watch?v=kDsFP67_ZSE).
>>> from fipy import CellVariable, Gmsh2DIn3DSpace, GaussianNoiseVariable, Viewer, TransientTerm, DiffusionTerm, DefaultSolver
>>> from fipy.tools import numerix
The only difference from examples.cahnHilliard.mesh2D
is the
declaration of mesh
.
>>> mesh = Gmsh2DIn3DSpace('''
... radius = 5.0;
... cellSize = 0.3;
...
... // create inner 1/8 shell
... Point(1) = {0, 0, 0, cellSize};
... Point(2) = {-radius, 0, 0, cellSize};
... Point(3) = {0, radius, 0, cellSize};
... Point(4) = {0, 0, radius, cellSize};
... Circle(1) = {2, 1, 3};
... Circle(2) = {4, 1, 2};
... Circle(3) = {4, 1, 3};
... Line Loop(1) = {1, -3, 2} ;
... Ruled Surface(1) = {1};
...
... // create remaining 7/8 inner shells
... t1[] = Rotate {{0,0,1},{0,0,0},Pi/2} {Duplicata{Surface{1};}};
... t2[] = Rotate {{0,0,1},{0,0,0},Pi} {Duplicata{Surface{1};}};
... t3[] = Rotate {{0,0,1},{0,0,0},Pi*3/2} {Duplicata{Surface{1};}};
... t4[] = Rotate {{0,1,0},{0,0,0},-Pi/2} {Duplicata{Surface{1};}};
... t5[] = Rotate {{0,0,1},{0,0,0},Pi/2} {Duplicata{Surface{t4[0]};}};
... t6[] = Rotate {{0,0,1},{0,0,0},Pi} {Duplicata{Surface{t4[0]};}};
... t7[] = Rotate {{0,0,1},{0,0,0},Pi*3/2} {Duplicata{Surface{t4[0]};}};
...
... // create entire inner and outer shell
... Surface Loop(100)={1,t1[0],t2[0],t3[0],t7[0],t4[0],t5[0],t6[0]};
... ''', overlap=2).extrude(extrudeFunc=lambda r: 1.1 * r)
>>> phi = CellVariable(name=r"$\phi$", mesh=mesh)
We start the problem with random fluctuations about
>>> phi.setValue(GaussianNoiseVariable(mesh=mesh,
... mean=0.5,
... variance=0.01))
FiPy doesn’t plot or output anything unless you tell it to: If
MayaviClient
is available, we
can customize the view with a subclass of
MayaviDaemon
.
>>> if __name__ == "__main__":
... try:
... viewer = MayaviClient(vars=phi,
... datamin=0., datamax=1.,
... daemon_file="examples/cahnHilliard/sphereDaemon.py")
... except:
... viewer = Viewer(vars=phi,
... datamin=0., datamax=1.,
... xmin=-2.5, zmax=2.5)
For FiPy, we need to perform the partial derivative
manually and then put the equation in the canonical
form by decomposing the spatial derivatives
so that each
Term
is of a single, even order:
FiPy would automatically interpolate
D * a**2 * (1 - 6 * phi * (1 - phi))
onto the faces, where the diffusive flux is calculated, but we obtain
somewhat more accurate results by performing a linear interpolation from
phi
at cell centers to PHI
at face centers.
Some problems benefit from non-linear interpolations, such as harmonic or
geometric means, and FiPy makes it easy to obtain these, too.
>>> PHI = phi.arithmeticFaceValue
>>> D = a = epsilon = 1.
>>> eq = (TransientTerm()
... == DiffusionTerm(coeff=D * a**2 * (1 - 6 * PHI * (1 - PHI)))
... - DiffusionTerm(coeff=(D, epsilon**2)))
Because the evolution of a spinodal microstructure slows with time, we use exponentially increasing time steps to keep the simulation “interesting”. The FiPy user always has direct control over the evolution of their problem.
>>> dexp = -5
>>> elapsed = 0.
>>> if __name__ == "__main__":
... duration = 1000.
... else:
... duration = 1e-2
>>> while elapsed < duration:
... dt = min(100, numerix.exp(dexp))
... elapsed += dt
... dexp += 0.01
... eq.solve(phi, dt=dt, solver=DefaultSolver(precon=None))
... if __name__ == "__main__":
... viewer.plot()
examples.cahnHilliard.sphereDaemon module¶
- class examples.cahnHilliard.sphereDaemon.SphereDaemon¶
Bases:
MayaviDaemon
- __base_traits__ = {'application': <traits.ctrait.CTrait object>, 'log_mode': <traits.ctrait.CTrait object>, 'script': <traits.ctrait.CTrait object>, 'start_gui_event_loop': <traits.ctrait.CTrait object>, 'trait_added': <traits.ctrait.CTrait object>, 'trait_modified': <traits.ctrait.CTrait object>}¶
- __class_traits__ = {'application': <traits.ctrait.CTrait object>, 'log_mode': <traits.ctrait.CTrait object>, 'script': <traits.ctrait.CTrait object>, 'start_gui_event_loop': <traits.ctrait.CTrait object>, 'trait_added': <traits.ctrait.CTrait object>, 'trait_modified': <traits.ctrait.CTrait object>}¶
- __del__()¶
- __delattr__(name, /)¶
Implement delattr(self, name).
- __getattribute__(name, /)¶
Return getattr(self, name).
- __instance_traits__ = {}¶
- __listener_traits__ = {'_on_application_gui_started': ('method', {'pattern': 'application.gui:started', 'post_init': False, 'dispatch': 'same'})}¶
- __observer_traits__ = {}¶
- __prefix_traits__ = {'': <traits.ctrait.CTrait object>, '*': ['_traits_cache_', ''], '_traits_cache_': <traits.ctrait.CTrait object>}¶
- __setattr__(name, value, /)¶
Implement setattr(self, name, value).
- __view_traits__ = {}¶
- classmethod add_class_trait(name, *trait)¶
Adds a named trait attribute to this class.
Also adds the same attribute to all subclasses.
- Parameters:
name (str) – Name of the attribute to add.
*trait – A trait or a value that can be converted to a trait using Trait() Trait definition of the attribute. It can be a single value or a list equivalent to an argument list for the Trait() function.
- classmethod class_default_traits_view()¶
Returns the name of the default traits view for the class.
- classmethod class_editable_traits()¶
Returns an alphabetically sorted list of the names of non-event trait attributes associated with the current class.
- classmethod class_trait_names(**metadata)¶
Returns a list of the names of all trait attributes whose definitions match the set of metadata criteria specified.
This method is similar to the traits() method, but returns only the names of the matching trait attributes, not the trait definitions.
- Parameters:
**metadata – Criteria for selecting trait attributes.
- classmethod class_trait_view(name=None, view_element=None)¶
- classmethod class_trait_view_elements()¶
Returns the ViewElements object associated with the class.
The returned object can be used to access all the view elements associated with the class.
- classmethod class_traits(**metadata)¶
Returns a dictionary containing the definitions of all of the trait attributes of the class that match the set of metadata criteria.
The keys of the returned dictionary are the trait attribute names, and the values are their corresponding trait definition objects.
If no metadata information is specified, then all explicitly defined trait attributes defined for the class are returned.
Otherwise, the metadata keyword dictionary is assumed to define a set of search criteria for selecting trait attributes of interest. The metadata dictionary keys correspond to the names of trait metadata attributes to examine, and the values correspond to the values the metadata attribute must have in order to be included in the search results.
The metadata values either may be simple Python values like strings or integers, or may be lambda expressions or functions that return True if the trait attribute is to be included in the result. A lambda expression or function must receive a single argument, which is the value of the trait metadata attribute being tested. If more than one metadata keyword is specified, a trait attribute must match the metadata values of all keywords to be included in the result.
- Parameters:
**metadata – Criteria for selecting trait attributes.
- classmethod class_visible_traits()¶
Returns an alphabetically sorted list of the names of non-event trait attributes associated with the current class, that should be GUI visible
- clip_data(src)¶
- parse_command_line(argv)¶
Parse command line options.
- poll_file()¶
- run()¶
This function is called after the GUI has started. Override this to do whatever you want to do as a MayaVi script. If this is not overridden then an empty MayaVi application will be started.
Make sure all other MayaVi specific imports are made here! If you import MayaVi related code earlier you will run into difficulties. Use ‘self.script’ to script the mayavi engine.
- classmethod set_trait_dispatch_handler(name, klass, override=False)¶
Sets a trait notification dispatch handler.
- setup_source(fname)¶
Given a VTK file name fname, this creates a mayavi2 reader for it and adds it to the pipeline. It returns the reader created.
- trait_added¶
An event fired when a new trait is dynamically added to the object. The value is the name of the trait that was added.
- trait_items_event(name, event_object, event_trait)¶
Fire an items event for changes to a Traits collection.
- Parameters:
name (str) – Name of the item trait for which an event is being fired. (The name will usually end in ‘_items’.)
event_object (object) – Object of type
TraitListEvent
,TraitDictEvent
orTraitSetEvent
describing the changes to the underlying collection trait value.event_trait (CTrait) – The items trait, of trait type
Event
.
- trait_modified¶
An event that can be fired to indicate that the state of the object has been modified.
- trait_property_changed(name, old_value[, new_value])¶
Call notifiers when a trait property value is explicitly changed.
Calls trait and object notifiers for a property value change.
- Parameters:
name (str) – Name of the trait whose value has changed
old_value (any) – Old value for this trait.
new_value (any, optional) – New value for this trait. If the new value is not provided, it’s looked up on the object.
- classmethod trait_subclasses(all=False)¶
Returns a list of the immediate (or all) subclasses of this class.
- Parameters:
all (bool) – Indicates whether to return all subclasses of this class. If False, only immediate subclasses are returned.
- traits_init()¶
Perform any final object initialization needed.
For the CHasTraits base class, this method currently does nothing.
- traits_inited()¶
Get the initialization state of this object.
- Returns:
initialized – True if the object is initialized, else False.
- Return type:
- update_pipeline(source)¶
Override this to do something else if needed.
- wrappers = {'extended': <class 'traits.trait_notifiers.ExtendedTraitChangeNotifyWrapper'>, 'fast_ui': <class 'traits.trait_notifiers.FastUITraitChangeNotifyWrapper'>, 'new': <class 'traits.trait_notifiers.NewTraitChangeNotifyWrapper'>, 'same': <class 'traits.trait_notifiers.TraitChangeNotifyWrapper'>, 'ui': <class 'traits.trait_notifiers.FastUITraitChangeNotifyWrapper'>}¶
Mapping from dispatch type to notification wrapper class type
examples.cahnHilliard.tanh1D module¶
This example solves the Cahn-Hilliard equation given by,
where the free energy functional is given by,
The Cahn-Hilliard equation can be rewritten in the following form,
The above form of the equation makes the non-linearity part of the diffusion coefficient for the first term on the RHS. This is the correct way to express the equation to FiPy.
We solve the problem on a 1D mesh
>>> from fipy import CellVariable, Grid1D, NthOrderBoundaryCondition, DiffusionTerm, TransientTerm, LinearLUSolver, DefaultSolver, Viewer
>>> from fipy.tools import numerix
>>> L = 40.
>>> nx = 1000
>>> dx = L / nx
>>> mesh = Grid1D(dx=dx, nx=nx)
and create the solution variable
>>> var = CellVariable(
... name="phase field",
... mesh=mesh,
... value=1.)
The boundary conditions for this problem are
and
or
>>> BCs = (
... NthOrderBoundaryCondition(faces=mesh.facesLeft, value=0, order=2),
... NthOrderBoundaryCondition(faces=mesh.facesRight, value=0, order=2))
>>> var.constrain(1, mesh.facesRight)
>>> var.constrain(.5, mesh.facesLeft)
Using
>>> asq = 1.0
>>> epsilon = 1
>>> diffusionCoeff = 1
we create the Cahn-Hilliard equation:
>>> faceVar = var.arithmeticFaceValue
>>> freeEnergyDoubleDerivative = asq * ( 1 - 6 * faceVar * (1 - faceVar))
>>> diffTerm2 = DiffusionTerm(
... coeff=diffusionCoeff * freeEnergyDoubleDerivative)
>>> diffTerm4 = DiffusionTerm(coeff=(diffusionCoeff, epsilon**2))
>>> eqch = TransientTerm() == diffTerm2 - diffTerm4
>>> import fipy.solvers.solver
>>> if fipy.solvers.solver in ['pysparse', 'pyamgx']:
... solver = LinearLUSolver(tolerance=1e-15, iterations=100)
... else:
... solver = DefaultSolver()
The solution to this 1D problem over an infinite domain is given by,
or
>>> a = numerix.sqrt(asq)
>>> answer = 1 / (1 + numerix.exp(-a * (mesh.cellCenters[0]) / epsilon))
If we are running interactively, we create a viewer to see the results
>>> if __name__ == '__main__':
... viewer = Viewer(vars=var, datamin=0., datamax=1.0)
... viewer.plot()
We iterate the solution to equilibrium and, if we are running interactively, we update the display and output data about the progression of the solution
>>> dexp=-5
>>> from builtins import range
>>> for step in range(100):
... dt = numerix.exp(dexp)
... dt = min(10, dt)
... dexp += 0.5
... eqch.solve(var=var, boundaryConditions=BCs, solver=solver, dt=dt)
... if __name__ == '__main__':
... diff = abs(answer - numerix.array(var))
... maxarg = numerix.argmax(diff)
... print('maximum error: {}'.format(diff[maxarg]))
... print('element id:', maxarg)
... print('value at element {} is {}'.format(maxarg, var[maxarg]))
... print('solution value: {}'.format(answer[maxarg]))
...
... viewer.plot()
We compare the analytical solution with the numerical result,
>>> print(var.allclose(answer, atol=1e-4))
1