Source code for fenicsxconcrete.experimental_setup.base_experiment

from abc import ABC, abstractmethod
from collections.abc import Callable

import dolfinx as df
import pint
import ufl

from fenicsxconcrete.boundary_conditions.boundary import plane_at, point_at
from fenicsxconcrete.util import LogMixin, Parameters, QuadratureRule, ureg


[docs] class Experiment(ABC, LogMixin): """base class for experimental setups Attributes: parameters : parameter dictionary with units p : parameter dictionary without units """ def __init__(self, parameters: dict[str, pint.Quantity]) -> None: """Initialises the parent object This is needs to be called by children Constant parameters are defined here Args: parameters: parameter dictionary with units """ # initialize parameter attributes setup_parameters = Parameters() # setting up default setup parameters defined in each child default_p = self.default_parameters() setup_parameters.update(default_p) # update with input parameters setup_parameters.update(parameters) # get logger info which parameters are set to default values # plus check dimensionality of input parameters keys_set_default = [] for key in dict(default_p): if key not in parameters: keys_set_default.append(key) else: # check if units are compatible dim_given = parameters[key].dimensionality dim_default = default_p[key].dimensionality if dim_given != dim_default: raise ValueError( f"given units for {key} are not compatible with default units: {dim_given} != {dim_default}" ) self.logger.info(f"for the following parameters, the default values are used: {keys_set_default}") # as attribute self.parameters = setup_parameters # remove units for use in fem model self.p = self.parameters.to_magnitude() self.setup()
[docs] @abstractmethod def setup(self): """Is called by init, must be defined by child"""
[docs] @staticmethod @abstractmethod def default_parameters() -> dict[str, pint.Quantity]: """sets up a working set of parameter values as example must be defined in each child Returns: a dictionary with required parameters and a set of working values as example """ pass
[docs] @abstractmethod def create_displacement_boundary(self, V: df.fem.FunctionSpace) -> list[df.fem.bcs.DirichletBC] | None: """defines empty displacement boundary conditions (to be done in child) this function is abstract until there is a need for a material that does not need a displacement boundary once that is required, just make this a normal function that returns an empty list Args: V: function space Returns: if defined a list with displacement boundary conditions otherwise None """
[docs] def create_force_boundary(self, v: ufl.argument.Argument | None = None) -> ufl.form.Form | None: """defines empty force boundary (to be done in child) Args: v: test function Returns: if defined a form for the force otherwise None """ pass
[docs] def create_body_force(self, v: ufl.argument.Argument | None = None) -> ufl.form.Form | None: """defines empty body force function Args: v: test function Returns: if defined a form for the body force otherwise None """ pass
[docs] def create_body_force_am( self, v: ufl.argument.Argument | None = None, q_fd: df.fem.Function | None = None, rule: QuadratureRule | None = None, ) -> ufl.form.Form | None: """defines empty body force function for am case Args: v: test function q_fd: quadrature function given the loading increment where elements are active rule: rule for the quadrature function Returns: if defined a form for the body force otherwise None """ pass
[docs] def boundary_top(self) -> Callable: """specifies boundary: plane at top Returns: fct defining if dof is at boundary """ if self.p["dim"] == 2: return plane_at(self.p["height"], 1) elif self.p["dim"] == 3: return plane_at(self.p["height"], 2)
[docs] def boundary_bottom(self) -> Callable: """specifies boundary: plane at bottom Returns: fct defining if dof is at boundary """ if self.p["dim"] == 2: return plane_at(0.0, "y") elif self.p["dim"] == 3: return plane_at(0.0, "z")
[docs] def boundary_left(self) -> Callable: """specifies boundary: plane at left side Returns: fct defining if dof is at boundary """ if self.p["dim"] == 2: return plane_at(0.0, "x") elif self.p["dim"] == 3: return plane_at(0.0, "x")
[docs] def boundary_right(self) -> Callable: """specifies boundary: plane at left side Returns: fct defining if dof is at boundary """ if self.p["dim"] == 2: return plane_at(self.p["length"], "x") elif self.p["dim"] == 3: return plane_at(self.p["length"], "x")
[docs] def boundary_front(self) -> Callable: """specifies boundary: plane at front only for 3D case front plane Returns: fct defining if dof is at boundary """ if self.p["dim"] == 3: return plane_at(0.0, "y")
[docs] def boundary_back(self) -> Callable: """specifies boundary: plane at front only for 3D case front plane Returns: fct defining if dof is at boundary """ if self.p["dim"] == 3: return plane_at(self.p["width"], "y")