Source code for festim.initial_condition

from collections.abc import Callable
from typing import Union

from mpi4py import MPI

import adios4dolfinx
import dolfinx
import numpy as np
import ufl
from dolfinx import fem

from festim.helpers import get_interpolation_points
from festim.species import Species
from festim.subdomain.volume_subdomain import VolumeSubdomain


[docs] class InitialConditionBase: """Base initial condition class. Args: value: the value of the initial condition. volume: the volume subdomain where the initial condition is applied Attributes: value: the value of the initial condition. volume: the volume subdomain where the initial condition is applied """ value: ( float | int | fem.Constant | np.ndarray | fem.Expression | ufl.core.expr.Expr | fem.Function ) volume: VolumeSubdomain def __init__( self, value: ( float | int | fem.Constant | np.ndarray | fem.Expression | ufl.core.expr.Expr | fem.Function ), volume: VolumeSubdomain, ): self.value = value self.volume = volume @property def volume(self): return self._volume @volume.setter def volume(self, value): # check that volume is festim.VolumeSubdomain if not isinstance(value, VolumeSubdomain): raise TypeError("volume must be of type festim.VolumeSubdomain") self._volume = value
[docs] class InitialConcentration(InitialConditionBase): """Initial concentration class. Args: value: the value of the initial concentration of a given species. species: the species to which the condition is applied volume: the volume subdomain where the initial condition is applied Attributes: value: the value of the initial concentration of a given species. species: the species to which the condition is applied volume: the volume subdomain where the initial condition is applied expr_fenics: the value of the initial condition in fenics expr format Examples: .. testsetup:: InitialConcentration from festim import InitialConcentration, Species, Material, VolumeSubdomain my_species = Species(name='test') dummy_mat = Material(D_0=1, E_D=0.1) my_vol = VolumeSubdomain(id=1, material=dummy_mat) .. testcode:: InitialConcentration InitialConcentration(value=1, species=my_species, volume=my_vol) InitialConcentration( value=lambda x: 1 + x[0], species=my_species, volume=my_vol ) InitialConcentration( value=lambda T: 1 + T, species=my_species, volume=my_vol ) InitialConcentration( value=lambda x, T: 1 + x[0] + T, species=my_species, volume=my_vol ) """ expr_fenics: Union[Callable, fem.Expression] species: Species def __init__(self, value, volume, species: Species): super().__init__(value=value, volume=volume) self.species = species self.expr_fenics = None @property def species(self): return self._species @species.setter def species(self, value): # check that species is festim.Species or list of festim.Species if not isinstance(value, Species): raise TypeError("species must be of type festim.Species") self._species = value
[docs] def create_expr_fenics( self, mesh: dolfinx.mesh.Mesh, temperature: fem.Function | fem.Constant, function_space: fem.functionspace, ): """Creates the expr_fenics of the initial condition. If the value is a float or int, a function is created with an array with the shape of the mesh and all set to the value. Otherwise, it is converted to a fem.Expression. Args: mesh: the mesh temperature: the temperature function_space: the function space of the species """ x = ufl.SpatialCoordinate(mesh) if isinstance(self.value, int | float): self.expr_fenics = lambda x: np.full(x.shape[1], self.value) elif isinstance(self.value, fem.Function): self.expr_fenics = self.value elif callable(self.value): arguments = self.value.__code__.co_varnames kwargs = {} if "t" in arguments: raise ValueError("Initial condition cannot be a function of time.") if "x" in arguments: kwargs["x"] = x if "T" in arguments: kwargs["T"] = temperature self.expr_fenics = fem.Expression( self.value(**kwargs), get_interpolation_points(function_space.element), )
[docs] class InitialTemperature(InitialConditionBase): """Initial temperature class. Args: value: the value of the initial temperature volume: the volume subdomain where the initial condition is applied Attributes: value: the value of the initial temperature volume: the volume subdomain where the initial condition is applied expr_fenics: the value of the initial condition in fenics expr format Examples: .. testsetup:: InitialTemperature from festim import InitialTemperature, Material, VolumeSubdomain dummy_mat = Material(D_0=1, E_D=0.1) my_vol = VolumeSubdomain(id=1, material=dummy_mat) .. testcode:: InitialTemperature InitialTemperature(value=1, volume=my_vol) InitialTemperature(value=lambda x: 1 + x[0], volume=my_vol) InitialTemperature(value=lambda x, t: 1 + x[0] + t, volume=my_vol) """ def __init__(self, value, volume): super().__init__(value=value, volume=volume) self.expr_fenics = None
[docs] def create_expr_fenics( self, mesh: dolfinx.mesh.Mesh, function_space: fem.functionspace, ): """Creates the expr_fenics of the initial condition. If the value is a float or int, a function is created with an array with the shape of the mesh and all set to the value. Otherwise, it is converted to a fem.Expression. Args: mesh: the mesh function_space: the function space of the species """ x = ufl.SpatialCoordinate(mesh) if isinstance(self.value, int | float): self.expr_fenics = lambda x: np.full(x.shape[1], self.value) elif isinstance(self.value, fem.Function): self.expr_fenics = self.value elif callable(self.value): arguments = self.value.__code__.co_varnames kwargs = {} if "t" in arguments: raise ValueError("Initial condition cannot be a function of time.") if "x" in arguments: kwargs["x"] = x self.expr_fenics = fem.Expression( self.value(**kwargs), get_interpolation_points(function_space.element), )
[docs] def read_function_from_file( filename: str, name: str, timestamp: int | float, family="P", order: int = 1, mesh: dolfinx.mesh.Mesh | None = None, ) -> fem.Function: """Read a function from a file. note:: The function is read from a file using adios4dolfinx. For more information see the [adios4dolfinx documentation](https://jsdokken.com/adios4dolfinx/README.html). Args: filename: the filename name: the name of the function timestamp: the timestamp of the function family: the family of the function space order: the order of the function space mesh: Mesh to create input space on. Returns: the function """ mesh_in = adios4dolfinx.read_mesh(filename, MPI.COMM_WORLD) V_in = fem.functionspace(mesh_in, (family, order)) u_in = fem.Function(V_in) adios4dolfinx.read_function( filename=filename, u=u_in, name=name, time=timestamp, ) if mesh is None: return u_in else: V = fem.functionspace(mesh, (family, order)) u = fem.Function(V) num_cells = mesh.topology.index_map(mesh.topology.dim).size_local cells = np.arange(num_cells, dtype=np.int32) padding = 1e2 * np.finfo(mesh.geometry.x.dtype).eps idata = fem.create_interpolation_data(V, V_in, cells=cells, padding=padding) u.interpolate_nonmatching(u_in, cells, idata) return u