Skip to content

Commit a936ef5

Browse files
committed
numerical stability check, initial conditions example, changelog
1 parent ee41d81 commit a936ef5

6 files changed

Lines changed: 341 additions & 178 deletions

File tree

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
# Changelog
22

3+
## [0.9.3] May 2026
4+
5+
### Added
6+
7+
- Initial conditions support. Model variables can now be initialized either as scalars (uniform across the tissue)
8+
or as arrays (node-wise values). All initial condition fields use the `init_*` prefix.
9+
See the **change_initial_conditions.py** example.
10+
11+
- Courant–Friedrichs–Lewy (CFL) condition check to warn about potential instability of the user-defined
12+
numerical parameters.
13+
14+
- Propagation tests for validating model conduction velocity.
15+
16+
- **Reentry and Spiral waves** tutorial.
17+
18+
### Fixes
19+
20+
- Adjusted diffusion coefficients (`D_model`) in Fenton–Karma and Mitchell–Schaeffer models
21+
to ensure physiological conduction velocities.
22+
23+
- Removed the unnecessary variable `irel` in the Courtemanche model (now treated as a parameter).
24+
25+
- Standardized variable and parameter names in the ten-Tusscher–Panfilov 2006 model.
26+
27+
- Fixed an API issue and a potential deadlock in AnimationBuilder2D/3D.
28+
29+
330
## [0.9.0] March 2026
431

532
### Added

Tutorials/Reentries and Spiral waves.ipynb renamed to Tutorials/Reentry and Spiral waves.ipynb

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"cell_type": "markdown",
55
"metadata": {},
66
"source": [
7-
"# Reentries and Spiral Waves\n",
7+
"# Reentry and Spiral Waves\n",
88
"\n",
99
"This tutorial introduces reentrant excitation patterns in cardiac tissue and demonstrates several classical approaches for their simulation using Finitewave.\n",
1010
"\n",
@@ -167,7 +167,7 @@
167167
},
168168
{
169169
"cell_type": "code",
170-
"execution_count": 13,
170+
"execution_count": null,
171171
"metadata": {},
172172
"outputs": [
173173
{
@@ -235,11 +235,8 @@
235235
"model.cardiac_tissue = tissue\n",
236236
"model.stim_sequence = stim_sequence\n",
237237
"\n",
238-
"# Initialize the model\n",
239-
"model.initialize()\n",
240-
"\n",
241238
"# Temporarily keep the right half refractory using 'v' variable\n",
242-
"model.init_v = np.zeros_like(model.u)\n",
239+
"model.init_v = np.zeros_like(tissue.mesh, dtype=np.float64)\n",
243240
"model.init_v[n // 2:, :] = 1.0\n",
244241
"\n",
245242
"model.run()\n",
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""
2+
Change initial conditions in the Luo-Rudy model
3+
======================================
4+
5+
Overview:
6+
---------
7+
This example demonstrates how to set spatially varying initial conditions
8+
in the Luo-Rudy 1991 cardiac model. Here we create a non-conductive obstacle in the center of the tissue
9+
and initialize an anatomical reentry by keeping the right half of the tissue refractory.
10+
11+
Simulation Setup:
12+
-----------------
13+
- Tissue Grid: A 256×256 cardiac tissue domain.
14+
- Non-conductive Obstacle: A circular non-conductive region is created in the center of
15+
the tissue to simulate an anatomical obstacle.
16+
- Stimulation: A localized stimulus is applied to the left half of the upper border.
17+
- Initial Conditions: The right half of the tissue is initialized in a refractory state by setting the gating
18+
variables 'h' and 'j' to 0, while the left half is initialized to 0.9 (non-refractory).
19+
- Time and Space Resolution:
20+
- Temporal step (dt): 0.01
21+
- Spatial resolution (dr): 0.4
22+
- Total simulation time (t_max): 200
23+
24+
Execution:
25+
----------
26+
1. Create a 2D cardiac tissue grid and define a non-conductive obstacle.
27+
2. Apply a stimulus to the left half of the upper border.
28+
3. Set up and initialize the Luo-Rudy 1991 model with spatially varying initial conditions.
29+
4. Run the simulation to observe wave propagation and interaction with the obstacle.
30+
5. Visualize the final membrane potential distribution, highlighting the non-conductive region.
31+
32+
How to use initial conditions:
33+
------------------------------
34+
Initial conditions can be set by defining attributes that start with 'init_'
35+
followed by the name of the model variable. In this example, we set 'init_h' and 'init_j'
36+
to create a refractory region in the right half of the tissue. This allows us to control
37+
the initial state of the tissue and observe how it affects wave propagation.
38+
"""
39+
40+
41+
import numpy as np
42+
import matplotlib.pyplot as plt
43+
import finitewave as fw
44+
45+
import numpy as np
46+
import matplotlib.pyplot as plt
47+
import finitewave as fw
48+
49+
50+
n = 300
51+
tissue = fw.CardiacTissue([n, n])
52+
53+
# Create a circular non-conductive obstacle in the center
54+
x, y = np.meshgrid(np.arange(n), np.arange(n), indexing="ij")
55+
56+
cx, cy = n // 2, n // 2
57+
radius = 70
58+
59+
obstacle = (x - cx) ** 2 + (y - cy) ** 2 < radius ** 2
60+
61+
tissue.mesh[obstacle] = 0 # non-conductive
62+
63+
64+
stim_sequence = fw.StimSequence()
65+
66+
# Stimulate only the left half of the upper border
67+
stim_sequence.add_stim(
68+
fw.StimVoltageCoord(
69+
time=0,
70+
volt_value=20,
71+
x1=0, x2=n // 2,
72+
y1=0, y2=n // 2,
73+
)
74+
)
75+
76+
model = fw.LuoRudy91()
77+
model.dt = 0.01
78+
model.dr = 0.5
79+
model.t_max = 200
80+
81+
model.cardiac_tissue = tissue
82+
model.stim_sequence = stim_sequence
83+
84+
# Here we use initial conditions to keep the right half of the tissue refractory,
85+
# which will prevent wave propagation in that region until it is released.
86+
#
87+
# Variables starts with init_ are used as initial conditions for the corresponding model variables.
88+
# You can set them to scalar values (all elements will have the same initial value) or to spatially varying arrays.
89+
# In this case, we set 'h' and 'j' to arrays that are 0 in the left half and 1 in the right half,
90+
# which corresponds to the refractory state in the Luo-Rudy model.
91+
model.init_h = np.full_like(tissue.mesh, 0.9, dtype=np.float64)
92+
model.init_h[n // 2:, :] = 0.0
93+
model.init_j = np.full_like(tissue.mesh, 0.9, dtype=np.float64)
94+
model.init_j[n // 2:, :] = 0.0
95+
96+
model.run()
97+
98+
# Show the potential map at the end of calculations.
99+
# We also overlay the obstacle in gray to visualize its location relative to the wave propagation.
100+
fig, axs = plt.subplots(ncols=1)
101+
plt.rcParams["axes.titley"] = -0.25
102+
obstacle_overlay = np.ma.masked_where(~obstacle, obstacle)
103+
axs.imshow(model.u)
104+
axs.imshow(obstacle_overlay, cmap='gray', alpha=1.0, vmin=0, vmax=1)
105+
plt.tight_layout()
106+
plt.show()

finitewave/core/model/cardiac_model.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ def run(self, initialize=True, num_of_threads=None):
141141
Whether to (re)initialize the model before running the simulation.
142142
Default is True.
143143
"""
144+
self._check_cfl_condition()
144145
if initialize:
145146
self.initialize()
146147

@@ -254,7 +255,39 @@ def reset_parameters_to_defaults(self):
254255
"""
255256
for name, value in self.default_parameters.items():
256257
setattr(self, name, value)
257-
258+
259+
def _check_cfl_condition(self, safety_factor=0.75):
260+
"""
261+
Checks the CFL stability condition for the explicit diffusion scheme and issues a warning
262+
if it is likely to be violated.
263+
"""
264+
if self.D_model is None:
265+
return
266+
267+
if self.D_model <= 0:
268+
return
269+
270+
dt_limit = self.dr**2 / (2 * self.cardiac_tissue.dimensions * self.D_model)
271+
dt_recommended = safety_factor * dt_limit
272+
273+
if self.dt > dt_limit:
274+
warnings.warn(
275+
(
276+
"The selected time step may violate the CFL stability "
277+
"condition for the explicit diffusion scheme.\n\n"
278+
f"Current values:\n"
279+
f" dt = {self.dt}\n"
280+
f" dr = {self.dr}\n"
281+
f" D_max = {self.D_model}\n"
282+
f" dimensions = {self.cardiac_tissue.dimensions}\n\n"
283+
f"Estimated stable dt <= {dt_limit:.6g}\n"
284+
f"Recommended dt <= {dt_recommended:.6g}\n\n"
285+
"Consider decreasing dt, increasing dr, or decreasing D."
286+
),
287+
RuntimeWarning,
288+
stacklevel=2
289+
)
290+
258291
def _initialize_variables_and_parameters(self, ops):
259292
self.default_parameters = ops.get_parameters()
260293
self.default_variables = ops.get_variables()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "finitewave"
7-
version = "0.9.1"
7+
version = "0.9.3"
88
requires-python = ">=3.10"
99
authors = [
1010
{ name="Timur Nezlobinsky", email="nezlobinsky@gmail.com" },

0 commit comments

Comments
 (0)