Source code for beat.bidomain_model

from __future__ import annotations
import logging
import dolfin

try:
    import ufl_legacy as ufl
    from ufl_legacy.core.expr import Expr
except ImportError:
    import ufl
    from ufl.core.expr import Expr

from .base_model import BaseModel, Stimulus


logger = logging.getLogger(__name__)


[docs] class BidomainModel(BaseModel): def __init__( self, mesh, time, M_i, M_e, I_s: Stimulus | ufl.Coefficient | None = None, I_a=None, v_=None, params=None, ): self._nullspace_basis = None # Store input self._M_i = M_i self._M_e = M_e self._I_a = I_a super().__init__(mesh=mesh, time=time, params=params, I_s=I_s) def _setup_state_space(self) -> None: # Set-up function spaces k = self.parameters["degree"] Ve = dolfin.FiniteElement("CG", self._mesh.ufl_cell(), k) V = dolfin.FunctionSpace(self._mesh, "CG", k) Ue = dolfin.FiniteElement("CG", self._mesh.ufl_cell(), k) dolfin.FunctionSpace(self._mesh, "CG", k) use_R = self.parameters["use_avg_u_constraint"] if use_R: Re = dolfin.FiniteElement("R", self._mesh.ufl_cell(), 0) dolfin.FunctionSpace(self._mesh, "R", 0) self.VUR = dolfin.FunctionSpace(self._mesh, dolfin.MixedElement((Ve, Ue, Re))) else: self.VUR = dolfin.FunctionSpace(self._mesh, dolfin.MixedElement((Ve, Ue))) self.V = V # Set-up solution fields: self.merger = dolfin.FunctionAssigner(V, self.VUR.sub(0)) self.v_ = dolfin.Function(V, name="v_") self._state = dolfin.Function(self.VUR, name="vur") @property def state(self) -> dolfin.Function: return self._state def assign_previous(self) -> None: self.merger.assign(self.v_, self.state.sub(0)) def _create_linear_solver(self): "Helper function for creating linear solver based on parameters." solver, update_routine = super()._create_linear_solver() solver_type = self.parameters["linear_solver_type"] if solver_type == "iterative": # Still waiting for that bug fix: solver.parameters.update({"convergence_norm_type": "preconditioned"}) solver.parameters.update(self.parameters["petsc_krylov_solver"]) # Set nullspace if present. We happen to know that the # transpose nullspace is the same as the nullspace (easy # to prove from matrix structure). if not self.parameters["use_avg_u_constraint"]: A = dolfin.as_backend_type(self._lhs_matrix) A.set_nullspace(self.nullspace) return solver, update_routine @property def nullspace(self): if self._nullspace_basis is None: null_vector = dolfin.Vector(self.state.vector()) self.VUR.sub(1).dofmap().set(null_vector, 1.0) null_vector *= 1.0 / null_vector.norm("l2") self._nullspace_basis = dolfin.VectorSpaceBasis([null_vector]) return self._nullspace_basis
[docs] @staticmethod def default_parameters(): """Initialize and return a set of default parameters *Returns* A set of parameters (:py:class:`dolfin.Parameters`) To inspect all the default parameters, do:: info(BidomainSolver.default_parameters(), True) """ params = super(BidomainModel, BidomainModel).default_parameters() params["use_avg_u_constraint"] = False return params
[docs] def variational_forms(self, k_n: Expr | float) -> tuple[ufl.Form, ufl.Form]: """Create the variational forms corresponding to the given discretization of the given system of equations. *Arguments* k_n (:py:class:`ufl.Expr` or float) The time step *Returns* (lhs, rhs) (:py:class:`tuple` of :py:class:`ufl.Form`) """ # Extract theta parameter and conductivities theta = self.parameters["theta"] M_i = self._M_i M_e = self._M_e # Define variational formulation use_R = self.parameters["use_avg_u_constraint"] if use_R: (v, u, l) = dolfin.TrialFunctions(self.VUR) (w, q, lamda) = dolfin.TestFunctions(self.VUR) else: (v, u) = dolfin.TrialFunctions(self.VUR) (w, q) = dolfin.TestFunctions(self.VUR) # Set-up measure and rhs from stimulus # (dz, rhs) = rhs_with_markerwise_field(self._I_s, self._mesh, w) dz = dolfin.dx rhs = self.G_stim(w) # Set-up variational problem Dt_v_k_n = v - self.v_ v_mid = theta * v + (1.0 - theta) * self.v_ theta_parabolic = ( ufl.inner(M_i * ufl.grad(v_mid), ufl.grad(w)) * dz() + ufl.inner(M_i * ufl.grad(u), ufl.grad(w)) * dz() ) theta_elliptic = ( ufl.inner(M_i * ufl.grad(v_mid), ufl.grad(q)) * dz() + ufl.inner((M_i + M_e) * ufl.grad(u), ufl.grad(q)) * dz() ) G = Dt_v_k_n * w * dz() + k_n * theta_parabolic + k_n * theta_elliptic - k_n * rhs if use_R: G += k_n * (lamda * u + l * q) * dz() # Add applied current as source in elliptic equation if # applicable if self._I_a: G -= k_n * self._I_a * q * dz() # (a, L) = dolfin.system(G) # return (a, L) return G, None