FESTIM v0.8

import FESTIM as F

parameters = {
    "mesh_parameters": {
        "vertices": [0, 1, 2, 3, 4]
        },
    "materials": [
        {
            "id": 1,
            "D_0": 4.1e-9,
            "E_D": 0.3
        },
        {
            "id": 2,
            "D_0": 8.7e-9,
            "E_D": 0.4
        }
        ],
    "boundary_conditions": [
        {
            "type": "dc",
            "surfaces": [1, 2],
            "value": 2
        }
        ],
    "temperature": {
        "type": "expression",
        "value": 2*F.x + F.t
        },
    "source_term": {
        "value": 1
        },
    "solving_parameters": {
        "absolute_tolerance": 1e-10,
        "relative_tolerance": 1e-10,
        "maximum_iterations": 50,
        "final_time": 10,
        "initial_stepsize": 0.1
        }
}

F.run(parameters)
import FESTIM as F

my_sim = F.Simulation()

my_sim.mesh = F.MeshFromVertices([0, 1, 2, 3, 4])

tungsten = F.Material(id=1, D_0=4.1e-9, E_D=0.3)
copper = F.Material(id=2, D_0=8e-7, E_D=0.4)

my_sim.materials = F.Materials(
	[tungsten, copper]
)

my_sim.boundary_conditions = [
	F.DirichletBC(surfaces=[1, 2], value=2)
]

my_sim.T = F.Temperature(2*F.x + F.t)

my_sim.sources = F.Source(1, volumes=1, field="solute")

my_sim.settings = F.Settings(
	absolute_tolerance=1e-10,
	relative_tolerance=1e-10,
	maximum_iterations=50,
	transient=True,
	final_time=10
)

my_sim.dt= F.Stepsize(0.1)

my_sim.initialise()
my_sim.run()

FESTIM 0.8

FESTIM 0.7

class Trap(F.Concentration):
    def __init__(
            self, k_0, E_k, p_0, E_p, materials, density, id=None):

Easy to document

Easy to document

class Trap(F.Concentration):
    def __init__(
            self, k_0, E_k, p_0, E_p, materials, density, id=None):
        """Inits Trap
        Args:
            k_0 (float): trapping pre-exponential factor
            E_k (float): trapping activation energy
            p_0 (float): detrapping pre-exponential factor
            E_p (float): detrapping activation energy
            materials (list or int): the materials ids the trap is living in
            density (sp.Add, float): the trap density
            id (int, optional): The trap id. Defaults to None.
        Raises:
            ValueError: if duplicates are found in materials
        """

Easy to extend

class CustomSource(F.Source):
    def __init__(self, A, B, volume):
        value = A*F.t + B*(F.x + F.y**2)

        super().__init__(value, volume, field="solute")
        
my_source = CustomSource(A=2, B=4, volume=1)
class Stepsize:

    def adapt(self, t, nb_it, converged):
        """Changes the stepsize based on convergence.
        Args:
            t (float): current time.
            nb_it (int): number of iterations the solver required to converge.
            converged (bool): True if the solver converged, else False.
        """
        change_ratio = self.adaptive_stepsize["stepsize_change_ratio"]
        dt_min = self.adaptive_stepsize["dt_min"]
        stepsize_stop_max = self.adaptive_stepsize["stepsize_stop_max"]
        t_stop = self.adaptive_stepsize["t_stop"]
        if not converged:
            self.value.assign(float(self.value)/change_ratio)
            if float(self.value) < dt_min:
                raise ValueError('stepsize reached minimal value')
        if nb_it < 5:
            self.value.assign(float(self.value)*change_ratio)
        else:
            self.value.assign(float(self.value)/change_ratio)

        if t_stop is not None:
            if t >= t_stop:
                if float(self.value) > stepsize_stop_max:
                    self.value.assign(stepsize_stop_max)
class Stepsize:

    def adapt(self, t, nb_it, converged):
        """Changes the stepsize based on convergence.
        Args:
            t (float): current time.
            nb_it (int): number of iterations the solver required to converge.
            converged (bool): True if the solver converged, else False.
        """
        change_ratio = self.adaptive_stepsize["stepsize_change_ratio"]
        dt_min = self.adaptive_stepsize["dt_min"]
        stepsize_stop_max = self.adaptive_stepsize["stepsize_stop_max"]
        t_stop = self.adaptive_stepsize["t_stop"]
        if not converged:
            self.value.assign(float(self.value)/change_ratio)
            if float(self.value) < dt_min:
                raise ValueError('stepsize reached minimal value')
        if nb_it < 5:
            self.value.assign(float(self.value)*change_ratio)
        else:
            self.value.assign(float(self.value)/change_ratio)

        if t_stop is not None:
            if t >= t_stop:
                if float(self.value) > stepsize_stop_max:
                    self.value.assign(stepsize_stop_max)
class CustomSource(F.Source):
    def __init__(self, A, B, volume):
        value = A*F.t + B*(F.x + F.y**2)

        super().__init__(value, volume, field="solute")
        
my_source = CustomSource(A=2, B=4, volume=1)

Easy to extend

class CustomStepsize(F.Stepsize):

    def adapt(self, t, nb_it, converged):
	 

Inheritance

Easy to extend

class CustomStepsize(F.Stepsize):

    def adapt(self, t, nb_it, converged):
      print('Adapting stepsize')
      if converged:
      	self.value *= 1.2 * (1 - nb_it/5)
      else:
        self.value *= 1/2
class RecombinationFlux(F.SurfaceFlux):
    def __init__(self, Kr_0, E_Kr, surface):
      super().__init__(field="solute", surface=surface)

      self.Kr_0 = Kr_0
      self.E_Kr = E_Kr

    def compute(self):
      Kr = self.Kr_0*exp(-self.E_Kr/F.k_B/self.T)
      return Kr*self.function**2
class MobileTrap(F.Trap):
  def create_form(self, **kwargs):
    super().create_form(**kwargs)

    self.F += 2 * dot(
      grad(self.solution), grad(self.test_function))*kwargs["dx"]
class LinearMesh(F.MeshFromVertices):
    def __init__(self, N, start_dx, ratio):
        dx = np.empty((N,))
        dx[0] = start_dx
        dx[1:] = ratio
        dx = np.cumprod(dx)
        vertices = np.concatenate([0], np.cumsum(dx))
        super().create_form(vertices)

Inheritance

Override methods

Easy to extend

class CustomStepsize(F.Stepsize):

    def adapt(self, t, nb_it, converged):
      print('Adapting stepsize')
      if converged:
      	self.value *= 1.2 * (1 - nb_it/5)
      else:
        self.value *= 1/2
class RecombinationFlux(F.SurfaceFlux):
    def __init__(self, Kr_0, E_Kr, surface):
      super().__init__(field="solute", surface=surface)

      self.Kr_0 = Kr_0
      self.E_Kr = E_Kr

    def compute(self):
      Kr = self.Kr_0*exp(-self.E_Kr/F.k_B/self.T)
      return Kr*self.function**2
class MobileTrap(F.Trap):
  def create_form(self, **kwargs):
    super().create_form(**kwargs)

    self.F += 2 * dot(
      grad(self.solution), grad(self.test_function))*kwargs["dx"]
class LinearMesh(F.MeshFromVertices):
    def __init__(self, N, start_dx, ratio):
        dx = np.empty((N,))
        dx[0] = start_dx
        dx[1:] = ratio
        dx = np.cumprod(dx)
        vertices = np.concatenate([0], np.cumsum(dx))
        super().create_form(vertices)

Easy to extend

Easy to read

10 files

FESTIM 0.7

47 files

FESTIM 0.8

\approx 138 \; \mathrm{lines/file}
\approx 32 \; \mathrm{lines/file}

10 files

FESTIM 0.7

47 files

FESTIM 0.8

\approx 138 \; \mathrm{lines/file}
\approx 32 \; \mathrm{lines/file}

Easy to read

10 files

FESTIM 0.7

47 files

FESTIM 0.8

\approx 138 \; \mathrm{lines/file}
\approx 32 \; \mathrm{lines/file}

Easy to read

Easy to

test

Smaller portions of code

=

Smaller tests

=

Better tests

Usage

Meshes

my_sim.mesh = my_mesh

Meshes

my_mesh = MeshFromVertices([0, 1, 2, 3, 4])

my_sim.mesh = my_mesh

Meshes

my_mesh = MeshFromRefinements(
    initial_number_of_cells=10,
    size=1,
    refinements=[{
        "x": 0.5, "cells": 10
    }]
)

my_sim.mesh = my_mesh

Meshes

my_mesh = MeshFromXDMF(
    volume_file="mesh.xdmf",
    boundary_file="boundaries.xdmf"
)


my_sim.mesh = my_mesh

Materials

my_sim.materials = my_materials

Materials

tungsten = F.Material(id=1, D_0=1, E_D=0.1)
copper = F.Material(id=2, D_0=2, E_D=0.2)

my_sim.materials = my_materials

Materials

tungsten = F.Material(id=1, D_0=1, E_D=0.1)
copper = F.Material(id=2, D_0=2, E_D=0.2)

my_materials = F.Materials([
  tungsten,
  copper,
])

my_sim.materials = my_materials

Traps

my_sim.traps = ...

Traps

trap1 = F.Trap(k_0=1, E_k=0.1, p_0=2, E_p=0.8, materials=[1, 2], density=10)
trap2 = F.Trap(k_0=1, E_k=0.1, p_0=3, E_p=1, materials=[1], density=1)


my_sim.traps = [trap1, trap2]
  • Trap

Traps

trap1 = F.Trap(k_0=1, E_k=0.1, p_0=2, E_p=0.8, materials=[1, 2], density=10)
trap2 = F.Trap(k_0=1, E_k=0.1, p_0=3, E_p=1, materials=[1], density=1)
trap3 = F.ExtrinsicTrap(
  k_0=1, E_k=0.1, p_0=3, E_p=1, materials=[1],
  form_parameters=...
)


my_sim.traps = [trap1, trap2, trap3]
  • Trap
  • ExtrinsicTrap

Traps

trap1 = F.Trap(k_0=1, E_k=0.1, p_0=2, E_p=0.8, materials=[1, 2], density=10)
trap2 = F.Trap(k_0=1, E_k=0.1, p_0=3, E_p=1, materials=[1], density=1)
trap3 = F.ExtrinsicTrap(
  k_0=1, E_k=0.1, p_0=3, E_p=1, materials=[1],
  form_parameters=...
)


my_sim.traps = [trap1, trap2, trap3]
  • Trap
  • ExtrinsicTrap
  • NeutronInducedTrap?

Exports

my_sim.exports = F.Exports(list_of_exports)

Exports

list_of_exports = [
  F.TXTExport("solute", times=[10, 100], label="mobile", folder="out"),
]


my_sim.exports = F.Exports(list_of_exports)

Exports

list_of_exports = [
  F.TXTExport("solute", times=[10, 100], label="mobile", folder="out"),
  F.XDMFExport("retention", label="retention", folder="out"),
]


my_sim.exports = F.Exports(list_of_exports)

Exports

list_of_exports = [
  F.TXTExport("solute", times=[10, 100], label="mobile", folder="out"),
  F.XDMFExport("retention", label="retention", folder="out"),
  derived_quantities
]


my_sim.exports = F.Exports(list_of_exports)

Exports

derived_quantities = F.DerivedQuantities(
  [
    F.AverageVolume("retention", volume=1)
  ]
)

list_of_exports = [
  F.TXTExport("solute", times=[10, 100], label="mobile", folder="out"),
  F.XDMFExport("retention", label="retention", folder="out"),
  derived_quantities
]


my_sim.exports = F.Exports(list_of_exports)

Exports

derived_quantities = F.DerivedQuantities(
  [
    F.AverageVolume("retention", volume=1),
    F.HydrogenFlux(surface=2)
  ]
)

list_of_exports = [
  F.TXTExport("solute", times=[10, 100], label="mobile", folder="out"),
  F.XDMFExport("retention", label="retention", folder="out"),
  derived_quantities
]


my_sim.exports = F.Exports(list_of_exports)

Exports

derived_quantities = F.DerivedQuantities(
  [
    F.AverageVolume("retention", volume=1),
    F.HydrogenFlux(surface=2),
    F.ThermalFlux(surface=[1, 2])
  ]
)

list_of_exports = [
  F.TXTExport("solute", times=[10, 100], label="mobile", folder="out"),
  F.XDMFExport("retention", label="retention", folder="out"),
  derived_quantities
]


my_sim.exports = F.Exports(list_of_exports)

Boundary Conditions

my_sim.boundary_conditions = list_of_bcs

Boundary Conditions

list_of_bcs = [
  F.DirichletBC(surfaces=1, value=10, component="T"),
]

my_sim.boundary_conditions = list_of_bcs
  • DirichletBC

Boundary Conditions

list_of_bcs = [
  F.DirichletBC(surfaces=1, value=10, component="T"),
  F.SievertsBC(surfaces=[2, 3], S_0=100, E_S=0.1, pressure=1e5)
]

my_sim.boundary_conditions = list_of_bcs
  • DirichletBC
  • SievertsBC

Boundary Conditions

list_of_bcs = [
  F.DirichletBC(surfaces=1, value=10, component="T"),
  F.SievertsBC(surfaces=[2, 3], S_0=100, E_S=0.1, pressure=1e5),
  F.ImplantationDirichlet(surfaces=4, phi=10, R_p=5e-9, D_0=1, E_D=0.1)
]

my_sim.boundary_conditions = list_of_bcs
  • DirichletBC
  • SievertsBC
  • ImplantationDirichlet

Boundary Conditions

def bc(T, cm, K):
  return K*T*cm

list_of_bcs = [
  F.DirichletBC(surfaces=1, value=10, component="T"),
  F.SievertsBC(surfaces=[2, 3], S_0=100, E_S=0.1, pressure=1e5),
  F.ImplantationDirichlet(surfaces=4, phi=10, R_p=5e-9, D_0=1, E_D=0.1)
]

my_sim.boundary_conditions = list_of_bcs
  • DirichletBC
  • SievertsBC
  • ImplantationDirichlet

Boundary Conditions

def bc(T, cm, K):
  return K*T*cm

list_of_bcs = [
  F.DirichletBC(surfaces=1, value=10, component="T"),
  F.SievertsBC(surfaces=[2, 3], S_0=100, E_S=0.1, pressure=1e5),
  F.ImplantationDirichlet(surfaces=4, phi=10, R_p=5e-9, D_0=1, E_D=0.1),
  F.CustomDirichlet(surfaces=5, function=bc, component="solute", K=2*F.t)
]

my_sim.boundary_conditions = list_of_bcs
  • DirichletBC
  • SievertsBC
  • ImplantationDirichlet
  • CustomDirichlet

Boundary Conditions

list_of_bcs = [
  F.DirichletBC(surfaces=1, value=10, component="T"),
  F.SievertsBC(surfaces=[2, 3], S_0=100, E_S=0.1, pressure=1e5),
  F.ImplantationDirichlet(surfaces=4, phi=10, R_p=5e-9, D_0=1, E_D=0.1),
  F.CustomDirichlet(surfaces=5, function=bc, component="solute", K=2*F.t),
  F.ConvectiveFlux(surfaces=6, h_coeff=10, T_ext=350)
]

my_sim.boundary_conditions = list_of_bcs
  • DirichletBC
  • SievertsBC
  • ImplantationDirichlet
  • CustomDirichlet
  • ConvectiveFlux

Boundary Conditions

list_of_bcs = [
  F.DirichletBC(surfaces=1, value=10, component="T"),
  F.SievertsBC(surfaces=[2, 3], S_0=100, E_S=0.1, pressure=1e5),
  F.ImplantationDirichlet(surfaces=4, phi=10, R_p=5e-9, D_0=1, E_D=0.1),
  F.CustomDirichlet(surfaces=5, function=bc, component="solute", K=2*F.t),
  F.ConvectiveFlux(surfaces=6, h_coeff=10, T_ext=350),
  F.RecombinationFlux(surfaces=7, Kr_0=10, E_Kr=0.1, order=2)
]

my_sim.boundary_conditions = list_of_bcs
  • DirichletBC
  • SievertsBC
  • ImplantationDirichlet
  • CustomDirichlet
  • ConvectiveFlux
  • RecombinationFlux

Boundary Conditions

list_of_bcs = [
  F.DirichletBC(surfaces=1, value=10, component="T"),
  F.SievertsBC(surfaces=[2, 3], S_0=100, E_S=0.1, pressure=1e5),
  F.ImplantationDirichlet(surfaces=4, phi=10, R_p=5e-9, D_0=1, E_D=0.1),
  F.CustomDirichlet(surfaces=5, function=bc, component="solute", K=2*F.t),
  F.ConvectiveFlux(surfaces=6, h_coeff=10, T_ext=350),
  F.RecombinationFlux(surfaces=7, Kr_0=10, E_Kr=0.1, order=2),
  F.FluxBC(surfaces=8, value=10 + sp.sin(F.t), component="solute")
]

my_sim.boundary_conditions = list_of_bcs
  • DirichletBC
  • SievertsBC
  • ImplantationDirichlet
  • CustomDirichlet
  • ConvectiveFlux
  • RecombinationFlux
  • FluxBC

Temperature

my_sim.T = my_temperature

Temperature

my_temperature = F.Temperature(350)

my_sim.T = my_temperature
  • Temperature (expression)

Temperature

my_temperature = F.HeatTransferProblem(transient=True, initial_value=350)

my_sim.T = my_temperature
  • Temperature (expression)
  • HeatTransferProblem

Settings

my_sim.settings = my_settings

Settings

my_settings = F.Settings(
  absolute_tolerance=1e10,
  relative_tolerance=1e-10,
)

my_sim.settings = my_settings

Many parameters have default values

Settings

my_settings = F.Settings(
  absolute_tolerance=1e10,
  relative_tolerance=1e-10,
  maximum_iterations=30,
  transient=True,
  final_time=10,
  chemical_pot=False,
  soret=False,
  traps_element_type="CG",
  update_jacobian=True,
  completion_tone=False
)

my_sim.settings = my_settings
def __init__(
  self,
  absolute_tolerance,
  relative_tolerance,
  maximum_iterations=30,
  transient=True,
  final_time=None,
  chemical_pot=False,
  soret=False,
  traps_element_type="CG",
  update_jacobian=True,
  completion_tone=False
):
"""Inits Settings
Args:
  absolute_tolerance (float): the absolute tolerance of the newton
      solver
  relative_tolerance (float): the relative tolerance of the newton
      solver
  maximum_iterations (int, optional): maximum iterations allowed for
      the solver to converge. Defaults to 30.
  transient (bool, optional): If set to True, the simulation will be
      transient. Defaults to True.
  final_time (float, optional): final time of the simulation.
      Defaults to None.
  chemical_pot (bool, optional): if True, conservation of chemical
      potential will be assumed. Defaults to False.
  soret (bool, optional): if True, Soret effect will be assumed.
      Defaults to False.
  traps_element_type (str, optional): Finite element used for traps.
      If traps densities are discontinuous (eg. different materials)
      "DG" is recommended. Defaults to "CG".
  update_jacobian (bool, optional): If set to False, the Jacobian of
      the formulation will be computed only once at the beggining.
      Else it will be computed at each time step. Defaults to True.
  completion_tone (bool, optional): If True, a native os alert
      tone will alert user upon completion of current run. Defaults
      to False.
        """

Everything is documented

Settings

my_settings = F.Settings(
  absolute_tolerance=1e10,
  relative_tolerance=1e-10,
  maximum_iterations=30,
  transient=True,
  final_time=10,
  chemical_pot=False,
  soret=False,
  traps_element_type="CG",
  update_jacobian=True,
  completion_tone=False
)

my_sim.settings = my_settings
def __init__(
  self,
  absolute_tolerance,
  relative_tolerance,
  maximum_iterations=30,
  transient=True,
  final_time=None,
  chemical_pot=False,
  soret=False,
  traps_element_type="CG",
  update_jacobian=True,
  completion_tone=False
):
"""Inits Settings
Args:
  absolute_tolerance (float): the absolute tolerance of the newton
      solver
  relative_tolerance (float): the relative tolerance of the newton
      solver
  maximum_iterations (int, optional): maximum iterations allowed for
      the solver to converge. Defaults to 30.
  transient (bool, optional): If set to True, the simulation will be
      transient. Defaults to True.
  final_time (float, optional): final time of the simulation.
      Defaults to None.
  chemical_pot (bool, optional): if True, conservation of chemical
      potential will be assumed. Defaults to False.
  soret (bool, optional): if True, Soret effect will be assumed.
      Defaults to False.
  traps_element_type (str, optional): Finite element used for traps.
      If traps densities are discontinuous (eg. different materials)
      "DG" is recommended. Defaults to "CG".
  update_jacobian (bool, optional): If set to False, the Jacobian of
      the formulation will be computed only once at the beggining.
      Else it will be computed at each time step. Defaults to True.
  completion_tone (bool, optional): If True, a native os alert
      tone will alert user upon completion of current run. Defaults
      to False.
        """

Everything is documented

Stepsize

my_sim.dt = my_stepsize

Stepsize

my_stepsize = F.Stepsize(
  initial_value=1.2,
  stepsize_change_change_ratio=1.2,
  dt_min=1e-8
)

my_sim.dt = my_stepsize

Options to stop the adaptive stepsize

Stepsize

my_stepsize = F.Stepsize(
  initial_value=1.2,
  stepsize_change_change_ratio=1.2,
  dt_min=1e-8,
  t_stop=100,
  stepsize_stop_max=2
)

my_sim.dt = my_stepsize

Options to stop the adaptive stepsize

ReadTheDocs

The best way to document code.

Requires FESTIM to be open-source 😀

Embed graphs in Slides

Embed webapps in Slides

Embed webapps in Slides

FESTIM 0.8

By Remi Delaporte-Mathurin

FESTIM 0.8

  • 354