finitewave 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- finitewave/README.md +20 -0
- finitewave/__init__.py +126 -0
- finitewave/core/__init__.py +8 -0
- finitewave/core/command/__init__.py +2 -0
- finitewave/core/command/command.py +52 -0
- finitewave/core/command/command_sequence.py +59 -0
- finitewave/core/exception/__init__.py +1 -0
- finitewave/core/exception/exceptions.py +46 -0
- finitewave/core/fibrosis/__init__.py +1 -0
- finitewave/core/fibrosis/fibrosis_pattern.py +51 -0
- finitewave/core/model/__init__.py +1 -0
- finitewave/core/model/cardiac_model.py +315 -0
- finitewave/core/model/ionic_kernel_generator.py +173 -0
- finitewave/core/state/__init__.py +2 -0
- finitewave/core/state/state_loader.py +80 -0
- finitewave/core/state/state_saver.py +122 -0
- finitewave/core/stencil/__init__.py +1 -0
- finitewave/core/stencil/stencil.py +46 -0
- finitewave/core/stimulation/__init__.py +4 -0
- finitewave/core/stimulation/stim.py +56 -0
- finitewave/core/stimulation/stim_current.py +44 -0
- finitewave/core/stimulation/stim_sequence.py +77 -0
- finitewave/core/stimulation/stim_voltage.py +49 -0
- finitewave/core/tissue/__init__.py +1 -0
- finitewave/core/tissue/cardiac_tissue.py +119 -0
- finitewave/core/tracker/__init__.py +2 -0
- finitewave/core/tracker/tracker.py +101 -0
- finitewave/core/tracker/tracker_sequence.py +64 -0
- finitewave/cpuwave/__init__.py +26 -0
- finitewave/cpuwave/exception/__init__.py +1 -0
- finitewave/cpuwave/exception/exceptions_2d.py +37 -0
- finitewave/cpuwave/fibrosis/__init__.py +2 -0
- finitewave/cpuwave/fibrosis/diffuse_pattern.py +82 -0
- finitewave/cpuwave/fibrosis/structural_pattern.py +122 -0
- finitewave/cpuwave/model/__init__.py +8 -0
- finitewave/cpuwave/model/_kernel_builder.py +46 -0
- finitewave/cpuwave/model/_registry.py +68 -0
- finitewave/cpuwave/model/aliev_panfilov.py +181 -0
- finitewave/cpuwave/model/barkley.py +157 -0
- finitewave/cpuwave/model/bueno_orovio.py +252 -0
- finitewave/cpuwave/model/courtemanche.py +553 -0
- finitewave/cpuwave/model/fenton_karma.py +211 -0
- finitewave/cpuwave/model/luo_rudy_91.py +186 -0
- finitewave/cpuwave/model/mitchell_schaeffer.py +184 -0
- finitewave/cpuwave/model/ten_tusscher_panfilov_2006.py +470 -0
- finitewave/cpuwave/stencil/__init__.py +5 -0
- finitewave/cpuwave/stencil/sten2D/__init__.py +3 -0
- finitewave/cpuwave/stencil/sten2D/asymmetric_stencil_2d.py +433 -0
- finitewave/cpuwave/stencil/sten2D/isotropic_stencil_2d.py +204 -0
- finitewave/cpuwave/stencil/sten2D/symmetric_stencil_2d.py +250 -0
- finitewave/cpuwave/stencil/sten3D/__init__.py +2 -0
- finitewave/cpuwave/stencil/sten3D/asymmetric_stencil_3d.py +458 -0
- finitewave/cpuwave/stencil/sten3D/isotropic_stencil_3d.py +149 -0
- finitewave/cpuwave/stimulation/__init__.py +5 -0
- finitewave/cpuwave/stimulation/stim_current_area.py +100 -0
- finitewave/cpuwave/stimulation/stim_current_coord.py +104 -0
- finitewave/cpuwave/stimulation/stim_current_matrix.py +68 -0
- finitewave/cpuwave/stimulation/stim_voltage_coord.py +85 -0
- finitewave/cpuwave/stimulation/stim_voltage_matrix.py +51 -0
- finitewave/cpuwave/tracker/__init__.py +33 -0
- finitewave/cpuwave/tracker/action_potential_tracker.py +79 -0
- finitewave/cpuwave/tracker/activation_time_tracker.py +75 -0
- finitewave/cpuwave/tracker/animation_tracker.py +194 -0
- finitewave/cpuwave/tracker/ecg_tracker.py +171 -0
- finitewave/cpuwave/tracker/local_activation_time_tracker.py +101 -0
- finitewave/cpuwave/tracker/period_tracker.py +91 -0
- finitewave/cpuwave/tracker/spiral_wave_core_tracker.py +174 -0
- finitewave/cpuwave/tracker/variables_tracker.py +118 -0
- finitewave/tools/__init__.py +5 -0
- finitewave/tools/animation_2d_builder.py +132 -0
- finitewave/tools/animation_3d_builder.py +152 -0
- finitewave/tools/velocity_2d_calculation.py +115 -0
- finitewave/tools/velocity_3d_calculation.py +97 -0
- finitewave/tools/vis_mesh_builder_3d.py +118 -0
- finitewave-0.9.0.dist-info/METADATA +417 -0
- finitewave-0.9.0.dist-info/RECORD +78 -0
- finitewave-0.9.0.dist-info/WHEEL +4 -0
- finitewave-0.9.0.dist-info/licenses/LICENSE +21 -0
finitewave/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Finitewave
|
|
2
|
+
|
|
3
|
+
Package for a wide range of tasks in modeling cardiac electrophysiology using
|
|
4
|
+
finite-difference methods.
|
|
5
|
+
|
|
6
|
+
## Package structure
|
|
7
|
+
|
|
8
|
+
### core
|
|
9
|
+
|
|
10
|
+
Base classes subpackage. Use this subpackage to create your own implementation
|
|
11
|
+
and incorporate it in the package logic.
|
|
12
|
+
|
|
13
|
+
### cpuwave
|
|
14
|
+
|
|
15
|
+
Ready-to-use implementation for cardiac modeling problems. Contains prepared models,
|
|
16
|
+
tissue generation methods, optimized numerical schemes and specialized tools.
|
|
17
|
+
|
|
18
|
+
### tools
|
|
19
|
+
|
|
20
|
+
Additional methods to treat the results and make different visual representations.
|
finitewave/__init__.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
finitewave
|
|
4
|
+
==========
|
|
5
|
+
|
|
6
|
+
A Python package for simulating cardiac electrophysiology in 2D and 3D using
|
|
7
|
+
the finite difference method.
|
|
8
|
+
|
|
9
|
+
This package provides a set of tools for simulating cardiac electrophysiology
|
|
10
|
+
in 2D and 3D using the finite difference method. The package includes classes
|
|
11
|
+
for creating cardiac tissue models, tracking electrical activity, and
|
|
12
|
+
visualizing simulation results. The package is designed to be flexible and
|
|
13
|
+
extensible, allowing users to create custom models and trackers for their
|
|
14
|
+
specific research needs.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from finitewave.core import (
|
|
19
|
+
Command,
|
|
20
|
+
CommandSequence,
|
|
21
|
+
FibrosisPattern,
|
|
22
|
+
CardiacModel,
|
|
23
|
+
StateLoader,
|
|
24
|
+
StateSaver,
|
|
25
|
+
StateSaverCollection,
|
|
26
|
+
Stencil,
|
|
27
|
+
StimCurrent,
|
|
28
|
+
StimSequence,
|
|
29
|
+
StimVoltage,
|
|
30
|
+
Stim,
|
|
31
|
+
CardiacTissue,
|
|
32
|
+
Tracker,
|
|
33
|
+
TrackerSequence
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
from finitewave.cpuwave import (
|
|
37
|
+
# IncorrectWeightsModeError2D,
|
|
38
|
+
DiffusePattern,
|
|
39
|
+
StructuralPattern,
|
|
40
|
+
AlievPanfilov,
|
|
41
|
+
Barkley,
|
|
42
|
+
MitchellSchaeffer,
|
|
43
|
+
FentonKarma,
|
|
44
|
+
BuenoOrovio,
|
|
45
|
+
LuoRudy91,
|
|
46
|
+
TenTusscherPanfilov2006,
|
|
47
|
+
Courtemanche,
|
|
48
|
+
AsymmetricStencil2D,
|
|
49
|
+
# SymmetricStencil2D,
|
|
50
|
+
IsotropicStencil2D,
|
|
51
|
+
StimCurrentArea,
|
|
52
|
+
StimCurrentCoord,
|
|
53
|
+
StimVoltageCoord,
|
|
54
|
+
StimCurrentMatrix,
|
|
55
|
+
StimVoltageMatrix,
|
|
56
|
+
ActionPotentialTracker,
|
|
57
|
+
ActivationTimeTracker,
|
|
58
|
+
AnimationTracker,
|
|
59
|
+
ECGTracker,
|
|
60
|
+
LocalActivationTimeTracker,
|
|
61
|
+
PeriodTracker,
|
|
62
|
+
# PeriodAnimationTracker,
|
|
63
|
+
SpiralWaveCoreTracker,
|
|
64
|
+
VariablesTracker,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
from finitewave.tools import (
|
|
68
|
+
Animation2DBuilder,
|
|
69
|
+
Animation3DBuilder,
|
|
70
|
+
VisMeshBuilder3D,
|
|
71
|
+
Velocity2DCalculation,
|
|
72
|
+
Velocity3DCalculation,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# compatibility with older versions:
|
|
77
|
+
|
|
78
|
+
CardiacTissue2D = CardiacTissue
|
|
79
|
+
CardiacTissue3D = CardiacTissue
|
|
80
|
+
|
|
81
|
+
AlievPanfilov2D = AlievPanfilov
|
|
82
|
+
AlievPanfilov3D = AlievPanfilov
|
|
83
|
+
Barkley2D = Barkley
|
|
84
|
+
Barkley3D = Barkley
|
|
85
|
+
MitchellSchaeffer2D = MitchellSchaeffer
|
|
86
|
+
MitchellSchaeffer3D = MitchellSchaeffer
|
|
87
|
+
FentonKarma2D = FentonKarma
|
|
88
|
+
FentonKarma3D = FentonKarma
|
|
89
|
+
BuenoOrovio2D = BuenoOrovio
|
|
90
|
+
BuenoOrovio3D = BuenoOrovio
|
|
91
|
+
LuoRudy912D = LuoRudy91
|
|
92
|
+
LuoRudy913D = LuoRudy91
|
|
93
|
+
TP062D = TenTusscherPanfilov2006
|
|
94
|
+
TP063D = TenTusscherPanfilov2006
|
|
95
|
+
Courtemanche2D = Courtemanche
|
|
96
|
+
Courtemanche3D = Courtemanche
|
|
97
|
+
|
|
98
|
+
StimCurrentArea2D = StimCurrentArea
|
|
99
|
+
StimCurrentArea3D = StimCurrentArea
|
|
100
|
+
StimCurrentCoord2D = StimCurrentCoord
|
|
101
|
+
StimCurrentCoord3D = StimCurrentCoord
|
|
102
|
+
StimVoltageCoord2D = StimVoltageCoord
|
|
103
|
+
StimVoltageCoord3D = StimVoltageCoord
|
|
104
|
+
StimCurrentMatrix2D = StimCurrentMatrix
|
|
105
|
+
StimCurrentMatrix3D = StimCurrentMatrix
|
|
106
|
+
StimVoltageMatrix2D = StimVoltageMatrix
|
|
107
|
+
StimVoltageMatrix3D = StimVoltageMatrix
|
|
108
|
+
|
|
109
|
+
ActionPotential2DTracker = ActionPotentialTracker
|
|
110
|
+
ActionPotential3DTracker = ActionPotentialTracker
|
|
111
|
+
ActivationTime2DTracker = ActivationTimeTracker
|
|
112
|
+
ActivationTime3DTracker = ActivationTimeTracker
|
|
113
|
+
Animation2DTracker = AnimationTracker
|
|
114
|
+
Animation3DTracker = AnimationTracker
|
|
115
|
+
ECG2DTracker = ECGTracker
|
|
116
|
+
ECG3DTracker = ECGTracker
|
|
117
|
+
LocalActivationTime2DTracker = LocalActivationTimeTracker
|
|
118
|
+
LocalActivationTime3DTracker = LocalActivationTimeTracker
|
|
119
|
+
Period2DTracker = PeriodTracker
|
|
120
|
+
Period3DTracker = PeriodTracker
|
|
121
|
+
SpiralWaveCore2DTracker = SpiralWaveCoreTracker
|
|
122
|
+
SpiralWaveCore3DTracker = SpiralWaveCoreTracker
|
|
123
|
+
Variables2DTracker = VariablesTracker
|
|
124
|
+
Variables3DTracker = VariablesTracker
|
|
125
|
+
MultiVariable2DTracker = VariablesTracker
|
|
126
|
+
MultiVariable3DTracker = VariablesTracker
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from finitewave.core.command import Command, CommandSequence
|
|
2
|
+
from finitewave.core.fibrosis import FibrosisPattern
|
|
3
|
+
from finitewave.core.model import CardiacModel
|
|
4
|
+
from finitewave.core.state import StateLoader, StateSaver, StateSaverCollection
|
|
5
|
+
from finitewave.core.stencil import Stencil
|
|
6
|
+
from finitewave.core.stimulation import StimCurrent, StimSequence, StimVoltage, Stim
|
|
7
|
+
from finitewave.core.tissue import CardiacTissue
|
|
8
|
+
from finitewave.core.tracker import Tracker, TrackerSequence
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Command(ABC):
|
|
5
|
+
"""Base class for a command to be executed during a simulation.
|
|
6
|
+
|
|
7
|
+
Attributes
|
|
8
|
+
----------
|
|
9
|
+
t : float
|
|
10
|
+
The time at which the command should be executed.
|
|
11
|
+
|
|
12
|
+
passed : bool
|
|
13
|
+
Flag indicating whether the command has been executed.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, time=None):
|
|
17
|
+
"""
|
|
18
|
+
Initializes a Command instance with the specified execution time.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
time : float
|
|
23
|
+
The time at which the command should be executed.
|
|
24
|
+
"""
|
|
25
|
+
self.t = time
|
|
26
|
+
self.passed = False
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def execute(self, model):
|
|
30
|
+
"""
|
|
31
|
+
Abstract method for executing the command. This method should be
|
|
32
|
+
implemented by subclasses to define the specific behavior of the
|
|
33
|
+
command.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
model : CardiacModel
|
|
38
|
+
The cardiac model instance on which the command will be executed.
|
|
39
|
+
"""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def update_status(self, model):
|
|
43
|
+
"""
|
|
44
|
+
Marks the command as executed.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
model : CardiacModel
|
|
49
|
+
The cardiac model instance on which the command was executed
|
|
50
|
+
"""
|
|
51
|
+
self.passed = model.t >= self.t
|
|
52
|
+
return self.passed
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class CommandSequence:
|
|
4
|
+
"""Manages a sequence of commands to be executed during a simulation.
|
|
5
|
+
|
|
6
|
+
Attributes
|
|
7
|
+
----------
|
|
8
|
+
sequence : list
|
|
9
|
+
A list of ``Command`` instances representing the sequence of commands
|
|
10
|
+
to be executed.
|
|
11
|
+
|
|
12
|
+
model : CardiacModel
|
|
13
|
+
The cardiac model instance on which commands will be executed.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.sequence = []
|
|
18
|
+
self.model = None
|
|
19
|
+
|
|
20
|
+
def initialize(self, model):
|
|
21
|
+
"""
|
|
22
|
+
Initializes the CommandSequence with the specified model and resets
|
|
23
|
+
the execution status of all commands.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
model : CardiacModel
|
|
28
|
+
The cardiac model instance to be used for command execution.
|
|
29
|
+
"""
|
|
30
|
+
self.model = model
|
|
31
|
+
for command in self.sequence:
|
|
32
|
+
command.passed = False
|
|
33
|
+
|
|
34
|
+
def add_command(self, command):
|
|
35
|
+
"""
|
|
36
|
+
Adds a ``Command`` instance to the sequence.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
command : Command
|
|
41
|
+
The command instance to be added to the sequence.
|
|
42
|
+
"""
|
|
43
|
+
self.sequence.append(command)
|
|
44
|
+
|
|
45
|
+
def remove_commands(self):
|
|
46
|
+
"""
|
|
47
|
+
Clears the sequence of all commands.
|
|
48
|
+
"""
|
|
49
|
+
self.sequence = []
|
|
50
|
+
|
|
51
|
+
def execute_next(self):
|
|
52
|
+
"""
|
|
53
|
+
Executes commands whose time has arrived and which have not been
|
|
54
|
+
executed yet.
|
|
55
|
+
"""
|
|
56
|
+
for command in self.sequence:
|
|
57
|
+
if not command.passed and command.update_status(self.model):
|
|
58
|
+
command.execute(self.model)
|
|
59
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from finitewave.core.exception.exceptions import IncorrectNumberOfWeights
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
class IncorrectWeightsShapeError(Exception):
|
|
4
|
+
def __init__(self, *args: object) -> None:
|
|
5
|
+
super().__init__(*args)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IncorrectNumberOfWeights(Exception):
|
|
9
|
+
"""Exception raised for errors in the shape of weights in the
|
|
10
|
+
``CardiacTissue`` class.
|
|
11
|
+
|
|
12
|
+
This exception is used to indicate that the shape of weights provided does
|
|
13
|
+
not match the expected dimensions. It includes details about the incorrect
|
|
14
|
+
shape and the expected shapes.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
number_of_weights : int
|
|
19
|
+
The number of weights in the incorrect shape.
|
|
20
|
+
|
|
21
|
+
n1 : int
|
|
22
|
+
The target number of weights in one dimension.
|
|
23
|
+
|
|
24
|
+
n2 : int
|
|
25
|
+
The target number of weights in another dimension.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, number_of_weights, n1, n2):
|
|
29
|
+
"""
|
|
30
|
+
Initializes the ``IncorrectNumberOfWeights`` with details about the
|
|
31
|
+
incorrect shape and expected dimensions.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
number_of_weights : int
|
|
36
|
+
The number of weights in the incorrect shape.
|
|
37
|
+
|
|
38
|
+
n1 : int
|
|
39
|
+
The target number of weights in one dimension.
|
|
40
|
+
|
|
41
|
+
n2 : int
|
|
42
|
+
The target number of weights in another dimension.
|
|
43
|
+
"""
|
|
44
|
+
self.message = (f"Number of weights provided ({number_of_weights})" +
|
|
45
|
+
f"does not match the expected {n1} or {n2}.")
|
|
46
|
+
super().__init__(self.message)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from finitewave.core.fibrosis.fibrosis_pattern import FibrosisPattern
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FibrosisPattern(ABC):
|
|
5
|
+
"""Abstract base class for generating and applying fibrosis patterns to
|
|
6
|
+
cardiac tissue.
|
|
7
|
+
|
|
8
|
+
This class defines an interface for creating fibrosis patterns and applying
|
|
9
|
+
them to cardiac tissue models. Subclasses must implement the ``generate``
|
|
10
|
+
method to define specific patterns. The ``apply`` method uses the generated
|
|
11
|
+
pattern to modify the mesh of the cardiac tissue.
|
|
12
|
+
"""
|
|
13
|
+
def __init__(self):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def generate(self, shape=None, mesh=None):
|
|
18
|
+
"""
|
|
19
|
+
Generates a fibrosis pattern for the given shape and optionally based
|
|
20
|
+
on the provided mesh.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
shape : tuple
|
|
25
|
+
The shape of the mesh (e.g., (ni, nj) or (ni, nj, nk)).
|
|
26
|
+
mesh : numpy.ndarray, optional
|
|
27
|
+
The existing mesh to base the pattern on. Default is None.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
numpy.ndarray
|
|
32
|
+
A new mesh array with the applied fibrosis pattern.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def apply(self, cardiac_tissue):
|
|
36
|
+
"""
|
|
37
|
+
Applies the generated fibrosis pattern to the specified cardiac tissue
|
|
38
|
+
object.
|
|
39
|
+
|
|
40
|
+
This method calls the ``generate`` method to create the pattern and
|
|
41
|
+
then updates the ``mesh`` attribute of the ``cardiac_tissue`` object
|
|
42
|
+
with the generated pattern.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
cardiac_tissue : CardiacTissue
|
|
47
|
+
The cardiac tissue object to which the fibrosis pattern will be
|
|
48
|
+
applied. The ``mesh`` attribute of this object will be updated with
|
|
49
|
+
the generated pattern.
|
|
50
|
+
"""
|
|
51
|
+
cardiac_tissue.mesh = self.generate(mesh=cardiac_tissue.mesh)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from finitewave.core.model.cardiac_model import CardiacModel
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import copy
|
|
3
|
+
import warnings
|
|
4
|
+
from tqdm import tqdm
|
|
5
|
+
import numpy as np
|
|
6
|
+
import numba
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CardiacModel(ABC):
|
|
10
|
+
"""
|
|
11
|
+
Base class for electrophysiological models.
|
|
12
|
+
|
|
13
|
+
This class serves as the base for implementing various cardiac models.
|
|
14
|
+
It provides methods for initializing the model, running simulations,
|
|
15
|
+
and managing the state of the simulation.
|
|
16
|
+
|
|
17
|
+
Attributes
|
|
18
|
+
----------
|
|
19
|
+
cardiac_tissue : CardiacTissue
|
|
20
|
+
The tissue object that represents the cardiac tissue in the simulation.
|
|
21
|
+
stim_sequence : StimSequence
|
|
22
|
+
The sequence of stimuli applied to the cardiac tissue.
|
|
23
|
+
tracker_sequence : TrackerSequence
|
|
24
|
+
The sequence of trackers used to monitor the simulation.
|
|
25
|
+
command_sequence : CommandSequence
|
|
26
|
+
The sequence of commands to execute during the simulation.
|
|
27
|
+
state_loader : StateLoader
|
|
28
|
+
The object responsible for loading the state of the simulation.
|
|
29
|
+
state_saver : StateSaver
|
|
30
|
+
The object responsible for saving the state of the simulation.
|
|
31
|
+
stencil : Stencil
|
|
32
|
+
The stencil used for numerical computations.
|
|
33
|
+
u : ndarray
|
|
34
|
+
Array representing the action potential (mV) across the tissue.
|
|
35
|
+
u_new : ndarray
|
|
36
|
+
Array for storing the updated action potential values.
|
|
37
|
+
dt : float
|
|
38
|
+
Time step for the simulation.
|
|
39
|
+
dr : float
|
|
40
|
+
Spatial step for the simulation.
|
|
41
|
+
t_max : float
|
|
42
|
+
Maximum time for the simulation (model units).
|
|
43
|
+
t : float
|
|
44
|
+
Current time in the simulation (model units).
|
|
45
|
+
step : int
|
|
46
|
+
Current step or iteration in the simulation.
|
|
47
|
+
D_model : float
|
|
48
|
+
Model-specific diffusion coefficient.
|
|
49
|
+
prog_bar : bool
|
|
50
|
+
Whether to display a progress bar during simulation.
|
|
51
|
+
npfloat : type
|
|
52
|
+
The floating-point type used for numerical computations.
|
|
53
|
+
state_vars : list
|
|
54
|
+
List of state variables to save and load during simulation.
|
|
55
|
+
"""
|
|
56
|
+
def __init__(self):
|
|
57
|
+
self.meta = {}
|
|
58
|
+
self.cardiac_tissue = None
|
|
59
|
+
self.stim_sequence = None
|
|
60
|
+
self.tracker_sequence = None
|
|
61
|
+
self.command_sequence = None
|
|
62
|
+
self.state_loader = None
|
|
63
|
+
self.state_saver = None
|
|
64
|
+
self.stencil = None
|
|
65
|
+
|
|
66
|
+
self.observers = []
|
|
67
|
+
self._buffs = [] # observer buffers
|
|
68
|
+
|
|
69
|
+
self.diffusion_kernel = None
|
|
70
|
+
self.ionic_kernel = None
|
|
71
|
+
|
|
72
|
+
self.u = np.ndarray
|
|
73
|
+
self.u_new = np.ndarray
|
|
74
|
+
self.weights = np.ndarray
|
|
75
|
+
self.dt = 0.
|
|
76
|
+
self.dr = 0.
|
|
77
|
+
self.t_max = 0.
|
|
78
|
+
self.t = 0
|
|
79
|
+
self.step = 0
|
|
80
|
+
self.D_model = 1.
|
|
81
|
+
|
|
82
|
+
self.prog_bar = True
|
|
83
|
+
self.npfloat = np.float64
|
|
84
|
+
self.state_vars = []
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
def run_ionic_kernel(self):
|
|
88
|
+
"""
|
|
89
|
+
Abstract method for running the ionic kernel. Must be implemented by
|
|
90
|
+
subclasses.
|
|
91
|
+
"""
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
def initialize(self):
|
|
95
|
+
"""
|
|
96
|
+
Initializes the model for simulation. Sets up arrays, computes weights,
|
|
97
|
+
and initializes stimuli, trackers, and commands.
|
|
98
|
+
"""
|
|
99
|
+
self.u = np.zeros_like(self.cardiac_tissue.mesh, dtype=self.npfloat)
|
|
100
|
+
self.u_new = self.u.copy()
|
|
101
|
+
self.step = 0
|
|
102
|
+
self.t = 0
|
|
103
|
+
|
|
104
|
+
self.compute_weights()
|
|
105
|
+
self.diffusion_kernel = self.stencil.select_diffusion_kernel()
|
|
106
|
+
|
|
107
|
+
if self.stim_sequence:
|
|
108
|
+
self.stim_sequence.initialize(self)
|
|
109
|
+
|
|
110
|
+
if self.tracker_sequence:
|
|
111
|
+
self.tracker_sequence.initialize(self)
|
|
112
|
+
|
|
113
|
+
if self.command_sequence:
|
|
114
|
+
self.command_sequence.initialize(self)
|
|
115
|
+
|
|
116
|
+
if self.state_loader:
|
|
117
|
+
self.state_loader.initialize(self)
|
|
118
|
+
|
|
119
|
+
if self.state_saver:
|
|
120
|
+
self.state_saver.initialize(self)
|
|
121
|
+
|
|
122
|
+
def compute_weights(self):
|
|
123
|
+
"""
|
|
124
|
+
Computes the weights for the stencil.
|
|
125
|
+
"""
|
|
126
|
+
self.cardiac_tissue.compute_myo_indexes()
|
|
127
|
+
|
|
128
|
+
if self.stencil is None:
|
|
129
|
+
self.stencil = self.select_stencil(self.cardiac_tissue)
|
|
130
|
+
|
|
131
|
+
self.weights = self.stencil.compute_weights(self, self.cardiac_tissue)
|
|
132
|
+
|
|
133
|
+
def run(self, initialize=True, num_of_threads=None):
|
|
134
|
+
"""
|
|
135
|
+
Runs the simulation loop. Handles stimuli, diffusion, ionic kernel
|
|
136
|
+
updates, and tracking.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
initialize : bool, optional
|
|
141
|
+
Whether to (re)initialize the model before running the simulation.
|
|
142
|
+
Default is True.
|
|
143
|
+
"""
|
|
144
|
+
if initialize:
|
|
145
|
+
self.initialize()
|
|
146
|
+
|
|
147
|
+
numba.set_num_threads(numba.config.NUMBA_NUM_THREADS)
|
|
148
|
+
|
|
149
|
+
if num_of_threads is not None:
|
|
150
|
+
if num_of_threads > numba.config.NUMBA_NUM_THREADS:
|
|
151
|
+
warnings.warn(
|
|
152
|
+
f"Selected number of threads ({num_of_threads}) exceeds the available threads ({numba.config.NUMBA_NUM_THREADS}). "
|
|
153
|
+
f"Using the maximum available threads instead."
|
|
154
|
+
)
|
|
155
|
+
num_of_theads = min(num_of_threads, numba.config.NUMBA_NUM_THREADS)
|
|
156
|
+
numba.set_num_threads(num_of_theads)
|
|
157
|
+
|
|
158
|
+
if self.t_max < self.t:
|
|
159
|
+
raise ValueError("t_max must be greater than current t.")
|
|
160
|
+
|
|
161
|
+
if self.state_loader:
|
|
162
|
+
self.state_loader.load()
|
|
163
|
+
|
|
164
|
+
iters = int(np.ceil((self.t_max - self.t) / self.dt))
|
|
165
|
+
bar_desc = f"Running {self.__class__.__name__}"
|
|
166
|
+
|
|
167
|
+
for _ in tqdm(range(iters), total=iters, desc=bar_desc,
|
|
168
|
+
disable=not self.prog_bar):
|
|
169
|
+
|
|
170
|
+
if self.stim_sequence:
|
|
171
|
+
self.stim_sequence.stimulate_next()
|
|
172
|
+
|
|
173
|
+
self.run_diffusion_kernel()
|
|
174
|
+
self.run_ionic_kernel()
|
|
175
|
+
|
|
176
|
+
if self.tracker_sequence:
|
|
177
|
+
self.tracker_sequence.tracker_next()
|
|
178
|
+
|
|
179
|
+
self.t += self.dt
|
|
180
|
+
self.step += 1
|
|
181
|
+
self.u_new, self.u = self.u, self.u_new
|
|
182
|
+
|
|
183
|
+
if self.command_sequence:
|
|
184
|
+
self.command_sequence.execute_next()
|
|
185
|
+
|
|
186
|
+
if self.state_saver:
|
|
187
|
+
self.state_saver.save()
|
|
188
|
+
|
|
189
|
+
if self.check_termination():
|
|
190
|
+
if self.state_saver:
|
|
191
|
+
self.state_saver.save()
|
|
192
|
+
break
|
|
193
|
+
|
|
194
|
+
def check_termination(self):
|
|
195
|
+
"""
|
|
196
|
+
Checks whether the simulation should terminate based on the current
|
|
197
|
+
time. The ``CommandSequence`` may change the ``t_max`` value during
|
|
198
|
+
execution to control the simulation duration.
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
bool
|
|
203
|
+
True if the simulation should terminate, False otherwise.
|
|
204
|
+
"""
|
|
205
|
+
max_iters = int(np.ceil(self.t_max / self.dt))
|
|
206
|
+
return (self.t > self.t_max) or (self.step > max_iters)
|
|
207
|
+
|
|
208
|
+
def run_diffusion_kernel(self):
|
|
209
|
+
"""
|
|
210
|
+
Executes the diffusion kernel computation using the current parameters
|
|
211
|
+
and tissue weights.
|
|
212
|
+
"""
|
|
213
|
+
self.diffusion_kernel(self.u_new, self.u, self.weights,
|
|
214
|
+
self.cardiac_tissue.myo_indexes)
|
|
215
|
+
|
|
216
|
+
@abstractmethod
|
|
217
|
+
def select_stencil(self, cardiac_tissue):
|
|
218
|
+
"""
|
|
219
|
+
Selects the appropriate stencil based on the cardiac tissue properties.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
cardiac_tissue : CardiacTissue
|
|
224
|
+
The tissue object representing the cardiac tissue.
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
Stencil
|
|
229
|
+
The stencil object to use for diffusion computations.
|
|
230
|
+
"""
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
def clone(self):
|
|
234
|
+
"""
|
|
235
|
+
Creates a deep copy of the current model instance.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
CardiacModel
|
|
240
|
+
A deep copy of the current CardiacModel instance.
|
|
241
|
+
"""
|
|
242
|
+
return copy.deepcopy(self)
|
|
243
|
+
|
|
244
|
+
def _initialize_variables_and_parameters(self, ops):
|
|
245
|
+
self.default_parameters = ops.get_parameters()
|
|
246
|
+
self.default_variables = ops.get_variables()
|
|
247
|
+
|
|
248
|
+
self.state_vars = self.default_variables.keys()
|
|
249
|
+
self.state_pars = list(self.default_parameters.keys())
|
|
250
|
+
|
|
251
|
+
# expose parameters as direct attributes (scalar or array)
|
|
252
|
+
for name, value in self.default_parameters.items():
|
|
253
|
+
setattr(self, name, value)
|
|
254
|
+
|
|
255
|
+
# expose initial conditions as init_*
|
|
256
|
+
for name, value in self.default_variables.items():
|
|
257
|
+
setattr(self, f"init_{name}", value)
|
|
258
|
+
|
|
259
|
+
# declare arrays (optional, for readability/debug)
|
|
260
|
+
for name in self.default_variables.keys():
|
|
261
|
+
setattr(self, name, np.ndarray)
|
|
262
|
+
|
|
263
|
+
def _allocate_state_arrays(self):
|
|
264
|
+
# allocate state arrays
|
|
265
|
+
for name in self.default_variables.keys():
|
|
266
|
+
init_val = getattr(self, f"init_{name}")
|
|
267
|
+
setattr(self, name, init_val * np.ones_like(self.u, dtype=self.npfloat))
|
|
268
|
+
if name == 'u':
|
|
269
|
+
self.u_new = self.u.copy()
|
|
270
|
+
|
|
271
|
+
# validate parameter fields shapes if they are arrays
|
|
272
|
+
tissue_shape = self.cardiac_tissue.mesh.shape
|
|
273
|
+
for name in self.default_parameters.keys():
|
|
274
|
+
par = getattr(self, name)
|
|
275
|
+
if isinstance(par, np.ndarray):
|
|
276
|
+
if par.shape != tissue_shape:
|
|
277
|
+
raise ValueError(
|
|
278
|
+
f"param '{name}' shape {par.shape} != tissue shape {tissue_shape}"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def _initialize_kernel(self, kernel, exclude_params=[]):
|
|
282
|
+
gen = kernel()
|
|
283
|
+
self._kernel_args_order = gen.args_order[:]
|
|
284
|
+
|
|
285
|
+
# args_order: state vars first, then all parameters (stable order for call site)
|
|
286
|
+
param_names = list(self.default_parameters.keys())
|
|
287
|
+
var_names = list(self.default_variables.keys())
|
|
288
|
+
|
|
289
|
+
# Tell generator which names are arrays vs scalars (for indexing decisions)
|
|
290
|
+
for name in var_names:
|
|
291
|
+
gen.arrays.append(name)
|
|
292
|
+
|
|
293
|
+
for name in param_names:
|
|
294
|
+
if name in exclude_params: # computed internally
|
|
295
|
+
continue
|
|
296
|
+
par = getattr(self, name)
|
|
297
|
+
if np.isscalar(par):
|
|
298
|
+
gen.scalars.append(name)
|
|
299
|
+
elif isinstance(par, np.ndarray):
|
|
300
|
+
gen.arrays.append(name)
|
|
301
|
+
|
|
302
|
+
return gen
|
|
303
|
+
|
|
304
|
+
def _form_and_verify_observers(self):
|
|
305
|
+
buffs = []
|
|
306
|
+
for obs in self.observers:
|
|
307
|
+
name = obs["name"]
|
|
308
|
+
try:
|
|
309
|
+
buffs.append(getattr(self, name))
|
|
310
|
+
except AttributeError as e:
|
|
311
|
+
raise AttributeError(
|
|
312
|
+
f"Observer buffer '{name}' not found on model. "
|
|
313
|
+
f"Create it before initialize(), e.g.: model.{name} = np.zeros(...)."
|
|
314
|
+
) from e
|
|
315
|
+
return buffs
|