Source code for festim.exports.custom_quantity

from collections.abc import Callable

import ufl
from dolfinx import fem
from scifem import assemble_scalar

from festim.exports.derived_quantity import DerivedQuantity
from festim.subdomain.surface_subdomain import SurfaceSubdomain
from festim.subdomain.volume_subdomain import VolumeSubdomain


[docs] class CustomQuantity(DerivedQuantity): r""" Export CustomQuantity. Args: expr: function that returns a UFL expression subdomain: subdomain on which the quantity is evaluated title: title of the exported quantity filename: name of the file to which the quantity is exported Attributes: expr: function that returns a UFL expression subdomain: subdomain on which the quantity is evaluated title: title of the exported quantity filename: name of the file to which the quantity is exported t: list of time values data: list of values of the quantity Usage: .. testcode:: import numpy as np import festim as F material = F.Material(D_0=1, E_D=0) volume = F.VolumeSubdomain(id=1, material=material) surface = F.SurfaceSubdomain(id=1, locator=lambda x: np.isclose(x[1], 1)) def total_concentration(**kwargs): return kwargs["A"] + kwargs["B"] quantity = F.CustomQuantity( expr=total_concentration, subdomain=volume, title="Total quantity", ) surface_quantity = F.CustomQuantity( expr=lambda **kwargs: -kwargs["D_A"] * ufl.dot( ufl.grad(kwargs["A"]), kwargs["n"] ), subdomain=surface, title="Surface flux", ) The callable passed to ``expr`` receives keyword arguments assembled by the problem class. Common entries are: ``A``, ``B``, ... Concentrations of the species present in the problem (here A and B). ``n`` The facet normal on the selected surface subdomain. ``T`` The temperature field. ``D_A``, ``D_B``, ... Species-specific diffusion coefficients. ``D`` The diffusion coefficient data, either a single field for one species or a dictionary keyed by species name when several species are present. ``x`` The spatial coordinate (x[0], x[1], x[2]). For a surface quantity, the returned UFL expression can represent a flux such as .. math:: q = -D\,\nabla c \cdot n and FESTIM will assemble .. math:: Q = \int_{\Gamma} q\,\mathrm{d}\Gamma over the selected surface subdomain. The expression returned by ``expr`` is treated as an integrand and assembled over the chosen subdomain. .. math:: Q = \int_{\Omega} q\,\mathrm{d}\Omega where ``q`` is the UFL expression returned by ``expr`` and ``\Omega`` is either a surface or a volume subdomain. """ def __init__( self, expr: Callable, subdomain: SurfaceSubdomain | VolumeSubdomain, title: str = "Custom Quantity", filename: str | None = None, ) -> None: super().__init__(filename=filename) self.expr = expr self.subdomain = subdomain self._title = title self.ufl_expr = None @property def title(self): return self._title
[docs] def compute(self, measure: ufl.Measure, entity_maps: dict | None = None): """Computes the value of the custom quantity and appends it to the data list. Args: measure: volume or surface measure of the model entity_maps: entity maps relating parent mesh and submesh """ if self.ufl_expr is None: raise ValueError("The UFL expression has not been evaluated yet.") form = fem.form( self.ufl_expr * measure(self.subdomain.id), entity_maps=entity_maps ) self.value = assemble_scalar(form) self.data.append(self.value)