Code structure#
This page lists the classes in beat, what each one is for and how they fit together. The notation
follows the mathematical background.
The big picture#
A tissue simulation in beat is three objects:
a PDE model that advances the transmembrane potential \(v\) by diffusion (e.g.
MonodomainModel),an ODE solver that advances the cell model at every point (e.g.
DolfinODESolver), anda coupling solver,
SplittingSolver, that alternates between the two by operator splitting.
Everything else plugs in around these: an eikonal solve gives activation times that drive the cell
model through a foot current (the reaction-eikonal model), ActivationTracker records activation
times from \(v\), and electrode recorders compute extracellular potentials from \(v\).
flowchart TD
eik["solve_eikonal / EikonalSolver"]
ode["ODE solver<br/>(DolfinODESolver, ...)"]
foot["FootCurrentODESolver(ode, t_a)<br/>(still an ODE solver)"]
pde["PDE model<br/>(MonodomainModel, ...)"]
split["SplittingSolver(pde or None, ode)"]
act["ActivationTracker<br/>(activation times)"]
rec["ElectrodeRecorder<br/>(ElectrodePotentials, LeadFieldElectrodes)"]
probe["PointEvaluator / scifem.evaluate_function<br/>(probes)"]
eik -- "t_a (optional: reaction-eikonal)" --> foot
ode --> foot
ode -. "v_ode ⇄ v_pde (pde_to_ode / ode_to_pde);<br/>ode.v_pde is pde.v" .- pde
foot --> split
ode -- "or directly" --> split
pde --> split
split -- "solver.v" --> act
split -- "solver.v" --> rec
split -- "solver.v" --> probe
Protocols#
The coupling solver and the electrogram tools only depend on small interfaces (Python
typing.Protocols). Any class with these members works, including your own.
PDEModel(beat.monodomain_solver): avproperty,assign_previous()andstep((t0, t1)). Implemented byMonodomainModel,BidomainModelandIrksomeMonodomainModel.ODESolver(beat.monodomain_solver):v_ode,v_pde,to_dolfin(),from_dolfin(),ode_to_pde(),pde_to_ode(),step(t0, dt),states_to_dolfin(names)andload_all_states(functions).ode.v_pdemust be the same Function aspde.v(passv_pde=pde.state). Implemented by everyBaseDolfinODESolversubclass and byFootCurrentODESolver.ElectrodeRecorder(beat.ecg): calling it returns{electrode name: potential}, the same on every rank, ready forLeadSystem.leads. Implemented byElectrodePotentialsandLeadFieldElectrodes. The two use different conductivity conventions:ECGRecoverytakes the monodomainM, the pseudo-bidomain classes the unscaled bidomain tensors.
Common wirings#
Monodomain (see the FitzHugh–Nagumo demo):
pde = beat.MonodomainModel(time=time, mesh=mesh, M=M, I_s=I_s, C_m=C_m)
ode = beat.odesolver.DolfinODESolver(v_ode=dolfinx.fem.Function(V_ode), v_pde=pde.state, **cell)
# cell: fun, init_states, parameters, num_states, v_index (see the demo)
solver = beat.SplittingSolver(pde=pde, ode=ode)
solver.solve((0.0, T), dt)
Reaction-eikonal with diffusion, R-E\(^+\) (a PDE model without stimulus):
t_a = beat.eikonal.solve_eikonal(mesh, velocities, (f0, s0, n0), sources)
ode = FootCurrentODESolver(cell_solver(v_pde=pde.state), t_a, ExponentialFoot())
solver = beat.SplittingSolver(pde=pde, ode=ode)
Reaction-eikonal without diffusion, R-E\(^-\) (no PDE; v_pde is not used):
ode = FootCurrentODESolver(cell_solver(v_pde=dolfinx.fem.Function(V)), t_a, ExponentialFoot())
solver = beat.SplittingSolver(pde=None, ode=ode)
Electrograms in a bath (see the wedge electrogram demo):
bath = beat.geometry.get_3D_wedge_bath_geometry(comm, dx)
conductor = VolumeConductor(
bath.geometry.mesh, bath.cell_tags, TISSUE, BATH, sigma_i_b, sigma_e_b, sigma_b
)
record = LeadFieldElectrodes(conductor, solver.v, sigma_i, {"P_E": wedge.electrode})
solver.step((t, t + dt))
phi_e = record()["P_E"]
Classes by role#
PDE models#
Class |
What it is for |
Builds on / implements |
Demos |
|---|---|---|---|
theta-scheme skeleton: assembles and solves one linear system per step, restart metadata, telemetry |
— |
(base class) |
|
the monodomain equation |
most demos, e.g. fitzhughnagumo |
||
the bidomain equations, \(v\) and \(u_e\) |
|||
monodomain advanced by an implicit Runge–Kutta method ( |
|||
|
|
— |
|
return value of |
|
— |
ODE solvers (cell models)#
Class |
What it is for |
Builds on / implements |
Demos |
|---|---|---|---|
cell states on a dolfinx space; a backend implements |
(base class) |
||
one cell model per region (marker value): region bookkeeping and per-region storage primitives |
(base class) |
||
one cell model everywhere, NumPy states, |
most demos, e.g. niederer_benchmark |
||
one cell model per region marker |
|||
the NumPy stepper inside |
— |
— |
|
cell model advanced by an implicit Runge–Kutta method ( |
|||
cell model evaluated through |
|||
a compiled C cell model, usable as |
callable |
Coupling#
Class |
What it is for |
Builds on / implements |
Demos |
|---|---|---|---|
Godunov/Strang splitting of a |
almost every demo |
||
deprecated name of |
— |
||
the protocols above |
|
— |
Eikonal and reaction-eikonal#
Class |
What it is for |
Builds on / implements |
Demos |
|---|---|---|---|
activation times by the Fast Iterative Method; set up once, solve for many sources ( |
C kernel |
||
an eikonal source: where, and when |
dataclass |
eikonal, the wedge demos |
|
parameters of the foot current |
dataclass |
the wedge demos |
|
wraps any ODE solver and adds the foot current at \(t_a\) |
Activation and probes#
Class |
What it is for |
Builds on / implements |
Demos |
|---|---|---|---|
per-dof activation time from \(v\) (max \(dv/dt\) or threshold crossing), updated each step |
— |
||
evaluates Functions at fixed points, locating them once |
— |
Electrograms and ECG#
Class |
What it is for |
Builds on / implements |
Demos |
|---|---|---|---|
pseudo-ECG in an infinite homogeneous medium |
— |
||
|
|||
extracellular potential \(\phi_e\) in a bounded tissue-and-bath volume (pseudo-bidomain) |
— |
||
\(\phi_e\) at named electrodes via lead fields (reciprocity), cheap per step |
|||
the protocol above |
|
— |
|
leads as weighted sums of electrode potentials ( |
dataclasses |
||
return value of |
|
— |
Geometry, stimulation and conductivities#
Class |
What it is for |
Builds on / implements |
Demos |
|---|---|---|---|
mesh, facet tags, markers and fibre fields |
|
||
the Neic 2017 transmural wedge: a |
wraps |
the wedge demos, eikonal |
|
the wedge inside a bath box: a |
wraps |
||
a stimulus current with its measure ( |
|
most demos |
|
longitudinal and transverse conductivity ( |
|
— |
Telemetry and single cell#
Class |
What it is for |
Builds on / implements |
Demos |
|---|---|---|---|
timing interface threaded through |
ABC |
— |
|
the default: records nothing |
— |
||
timings and Krylov statistics, with a summary |
|||
warnings from |
|
— |
The beat command-line interface (beat.cli) builds these same objects from a TOML file; see
the CLI guide.
When you add a public class, add it to this page: tests/test_architecture_docs.py checks that
every public class in src/beat is named here.