Source code for beat.irksome_model
import logging
from typing import Sequence
import basix
import dolfinx
import ufl
from .base_model import Results, Status, _transform_I_s
from .monodomain_model import MonodomainModel
from .stimulation import Stimulus
logger = logging.getLogger(__name__)
[docs]
class IrksomeMonodomainModel(MonodomainModel):
r"""Solve Monodomain model using Irksome for Runge-Kutta time stepping."""
def __init__(
self,
time: dolfinx.fem.Constant,
mesh: dolfinx.mesh.Mesh,
M: ufl.Coefficient | float,
butcher_tableau,
I_s: Stimulus | Sequence[Stimulus] | ufl.Coefficient | None = None,
params=None,
C_m: float = 1.0,
dx: ufl.Measure | None = None,
**kwargs,
):
try:
import irksome
except ImportError:
raise ImportError(
"The 'irksome' package is required for IrksomeMonodomainModel. "
"Install it with pip: 'pip install irksome[dolfinx]'.",
)
self._mesh = mesh
self.time = time
self.dx = dx or ufl.dx(domain=mesh)
self._M = M
self.C_m = dolfinx.fem.Constant(mesh, C_m)
self._I_s = _transform_I_s(I_s, dZ=self.dx)
self.butcher_tableau = butcher_tableau
self.parameters = MonodomainModel.default_parameters()
if params is not None:
self.parameters.update(params)
self._setup_state_space()
self._timestep = dolfinx.fem.Constant(mesh, self.parameters["default_timestep"])
# Define the continuous weak form for Irksome
v = self._state
w = ufl.TestFunction(self.V)
# F = C_m * Dt(v) * w + M * grad(v) * grad(w) - I_stim * w
F = (self.C_m * irksome.Dt(v) * w + ufl.inner(self._M * ufl.grad(v), ufl.grad(w))) * self.dx
F -= self._G_stim(w)
# Setup Irksome stepper
self.stepper = irksome.stage_derivative.StageDerivativeTimeStepper(
F,
self.butcher_tableau,
self.time,
self._timestep,
self._state,
bcs=[],
solver_parameters=self.parameters["petsc_options"],
backend="dolfinx",
)
def _setup_state_space(self) -> None:
k = self.parameters["degree"]
family = self.parameters["family"]
element = basix.ufl.element(family=family, cell=self._mesh.basix_cell(), degree=k)
self.V = dolfinx.fem.functionspace(self._mesh, element)
self._state = dolfinx.fem.Function(self.V, name="v")
@property
def state(self) -> dolfinx.fem.Function:
return self._state
def assign_previous(self):
# Irksome inherently updates the states within `advance()`,
# so manual history assignment isn't required here.
pass
def _G_stim(self, w):
return sum([i.expr * w * i.dz for i in self._I_s])
[docs]
def step(self, interval):
t0, t1 = interval
dt = t1 - t0
self._timestep.value = dt
self.time.value = t0
# Take the Runge-Kutta step
self.stepper.advance()
# Advance the constant time attribute manually
self.time.value = float(self.time) + dt
[docs]
def solve(self, interval, dt=None) -> Results:
T0, T = interval
if dt is None:
dt = T - T0
t0 = T0
t1 = T0 + dt
while t1 < T + 1e-12:
self.step((t0, t1))
t0 = t1
t1 = t0 + dt
return Results(state=self.state, status=Status.OK)