crossbridge 0.1.0__tar.gz
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.
- crossbridge-0.1.0/LICENSE +21 -0
- crossbridge-0.1.0/PKG-INFO +147 -0
- crossbridge-0.1.0/README.md +115 -0
- crossbridge-0.1.0/pyproject.toml +126 -0
- crossbridge-0.1.0/setup.cfg +4 -0
- crossbridge-0.1.0/src/crossbridge/__init__.py +56 -0
- crossbridge-0.1.0/src/crossbridge/base.py +80 -0
- crossbridge-0.1.0/src/crossbridge/land17.py +446 -0
- crossbridge-0.1.0/src/crossbridge/lewalle2024.py +534 -0
- crossbridge-0.1.0/src/crossbridge/rdq18.py +339 -0
- crossbridge-0.1.0/src/crossbridge/rdq20mf.py +541 -0
- crossbridge-0.1.0/src/crossbridge/utils.py +95 -0
- crossbridge-0.1.0/src/crossbridge.egg-info/PKG-INFO +147 -0
- crossbridge-0.1.0/src/crossbridge.egg-info/SOURCES.txt +19 -0
- crossbridge-0.1.0/src/crossbridge.egg-info/dependency_links.txt +1 -0
- crossbridge-0.1.0/src/crossbridge.egg-info/requires.txt +25 -0
- crossbridge-0.1.0/src/crossbridge.egg-info/top_level.txt +1 -0
- crossbridge-0.1.0/tests/test_land17.py +351 -0
- crossbridge-0.1.0/tests/test_lewalle2024.py +310 -0
- crossbridge-0.1.0/tests/test_rdq18.py +198 -0
- crossbridge-0.1.0/tests/test_rdq20mf.py +451 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Computational Physiology at Simula Research Laboratory
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: crossbridge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cardiac crossbridge models
|
|
5
|
+
Author-email: Henrik Finsberg <henriknf@simula.no>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: scipy
|
|
12
|
+
Provides-Extra: test
|
|
13
|
+
Requires-Dist: pytest; extra == "test"
|
|
14
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
15
|
+
Provides-Extra: demos
|
|
16
|
+
Requires-Dist: matplotlib; extra == "demos"
|
|
17
|
+
Requires-Dist: zero-mech; extra == "demos"
|
|
18
|
+
Requires-Dist: gotranx; extra == "demos"
|
|
19
|
+
Requires-Dist: scipy; extra == "demos"
|
|
20
|
+
Requires-Dist: tqdm; extra == "demos"
|
|
21
|
+
Requires-Dist: numba; extra == "demos"
|
|
22
|
+
Provides-Extra: docs
|
|
23
|
+
Requires-Dist: jupyter-book<2.0; extra == "docs"
|
|
24
|
+
Requires-Dist: jupytext; extra == "docs"
|
|
25
|
+
Requires-Dist: crossbridge[demos]; extra == "docs"
|
|
26
|
+
Requires-Dist: sphinxcontrib-bibtex; extra == "docs"
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: crossbridge[test]; extra == "all"
|
|
29
|
+
Requires-Dist: crossbridge[demos]; extra == "all"
|
|
30
|
+
Requires-Dist: crossbridge[docs]; extra == "all"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# crossbridge
|
|
34
|
+
|
|
35
|
+
**crossbridge** is a highly efficient, vectorized Python library for simulating cardiac myofilament activation and crossbridge dynamics.
|
|
36
|
+
|
|
37
|
+
## The Mathematical Models
|
|
38
|
+
`crossbridge` implements several reduced-order models of cardiac myofilament activation
|
|
39
|
+
([RDQ18](docs/models/rdq18.md), [RDQ20-MF](docs/models/rdq20mf.md), [Land2017](docs/models/land2017.md),
|
|
40
|
+
[Lewalle2024](docs/models/lewalle2024.md)), all sharing a common interface (see "Choosing a Model"
|
|
41
|
+
below) so that a coupled electromechanics simulation can swap between them with minimal code
|
|
42
|
+
changes. See [docs/models](docs/models/index.md) for a description and reference for each model.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
The package requires Python 3.11+. You can install the base package and its dependencies using `pip`.
|
|
47
|
+
|
|
48
|
+
To install the library from the source code:
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/ComputationalPhysiology/crossbridge.git
|
|
51
|
+
cd crossbridge
|
|
52
|
+
pip install .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
To install with optional dependencies (for running demos, tests, or building docs):
|
|
56
|
+
```bash
|
|
57
|
+
pip install ".[demos]" # Installs scipy, matplotlib, gotranx, numba, zero-mech, etc.
|
|
58
|
+
pip install ".[test]" # Installs pytest and coverage tools
|
|
59
|
+
pip install ".[docs]" # Installs jupyter-book and sphinx plugins
|
|
60
|
+
pip install ".[all]" # Installs everything
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Basic Usage
|
|
64
|
+
To most basic usage its to solve for a single cell. The `RDQ18` class provides an `advance_ODE` method that takes in the time step, calcium concentration, and sarcomere length to update the internal state of the model. This can then be used to compute an active tension based on the fraction of permissive crossbridges.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import numpy as np
|
|
68
|
+
from crossbridge import RDQ18, calcium_trace, sl_trace
|
|
69
|
+
|
|
70
|
+
# Initialize a model with 1000 cells/integration points
|
|
71
|
+
num_cells = 1
|
|
72
|
+
dt = 0.01
|
|
73
|
+
dt_sarc = 2.5e-5
|
|
74
|
+
sarcomere = RDQ18(num_cells=num_cells, Ta_max=60.0, params={"dt": dt_sarc})
|
|
75
|
+
|
|
76
|
+
# Inputs
|
|
77
|
+
t = np.arange(0, 1, dt) # Time array [s]
|
|
78
|
+
Ca = calcium_trace(t) # Calcium transient [uM]
|
|
79
|
+
SL = sl_trace(t) # Sarcomere length transient [um]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
Ta = np.zeros(len(t))
|
|
83
|
+
# Advance the ODEs by one time step
|
|
84
|
+
for i, (Cai, SLi) in enumerate(zip(Ca, SL)):
|
|
85
|
+
sarcomere.advance_ODE(dt, Cai, np.array([SLi]))
|
|
86
|
+
|
|
87
|
+
# Compute the fraction of permissive crossbridges (proxy for active tension)
|
|
88
|
+
permissivity = sarcomere.compute_permissivity()[0]
|
|
89
|
+
active_tension = sarcomere.Ta_max * permissivity
|
|
90
|
+
Ta[i] = active_tension
|
|
91
|
+
|
|
92
|
+
import matplotlib.pyplot as plt
|
|
93
|
+
|
|
94
|
+
fig, ax = plt.subplots(3, 1, sharex=True, figsize=(8, 6))
|
|
95
|
+
ax[0].plot(t, Ca)
|
|
96
|
+
ax[0].set_ylabel("Calcium [uM]")
|
|
97
|
+
ax[1].plot(t, SL)
|
|
98
|
+
ax[1].set_ylabel("Sarcomere Length [um]")
|
|
99
|
+
ax[2].plot(t, Ta)
|
|
100
|
+
ax[2].set_ylabel("Active Tension [kPa]")
|
|
101
|
+
ax[2].set_xlabel("Time [s]")
|
|
102
|
+
fig.tight_layout()
|
|
103
|
+
plt.show()
|
|
104
|
+
```
|
|
105
|
+

|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## Coupling to Electrophysiology and Mechanics
|
|
109
|
+
Most cellular and tissue-level simulations will require coupling the `RDQ18` model to electrophysiology and mechanics. The `advance_ODE` method is designed to be called at every time step of a larger simulation loop, allowing the sarcomere dynamics to evolve in response to changing calcium and length conditions.
|
|
110
|
+
|
|
111
|
+
The calcium concentration can be either a scalar (if all cells/integration points are assumed to have the same calcium transient) or an array with one entry per cell/integration point. The sarcomere length can similarly be a scalar or an array. Below is a pseudo-code example of how this coupling might look in a larger simulation loop:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
...
|
|
115
|
+
|
|
116
|
+
SL = np.full(num_cells, 2.2) # Initial sarcomere length [um]
|
|
117
|
+
for t in time_steps:
|
|
118
|
+
# Compute calcium from electrophysiology model
|
|
119
|
+
Cai = compute_calcium(t)
|
|
120
|
+
sarcomere.advance_ODE(dt, Cai, SL)
|
|
121
|
+
# Compute active tension and update mechanics
|
|
122
|
+
permissivity = sarcomere.compute_permissivity()[0]
|
|
123
|
+
active_tension = sarcomere.Ta_max * permissivity
|
|
124
|
+
# Update mechanics model with new active tension
|
|
125
|
+
# and compute new sarcomere length (SL) based on
|
|
126
|
+
# the mechanical response of the tissue
|
|
127
|
+
SL = compute_new_length(active_tension, SL)
|
|
128
|
+
...
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Examples & Demos
|
|
132
|
+
|
|
133
|
+
The `demo/` folder contains several scripts demonstrating how to couple the `crossbridge` model to different physics scales. See [demos](demo/index.md) for a description of each demo and how to run them.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
## Testing and Development
|
|
137
|
+
We use `pytest` for unit testing. To run the test suite and check code coverage:
|
|
138
|
+
```bash
|
|
139
|
+
pytest
|
|
140
|
+
```
|
|
141
|
+
To run the pre-commit linters (Ruff and MyPy):
|
|
142
|
+
```bash
|
|
143
|
+
pre-commit run --all-files
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# crossbridge
|
|
2
|
+
|
|
3
|
+
**crossbridge** is a highly efficient, vectorized Python library for simulating cardiac myofilament activation and crossbridge dynamics.
|
|
4
|
+
|
|
5
|
+
## The Mathematical Models
|
|
6
|
+
`crossbridge` implements several reduced-order models of cardiac myofilament activation
|
|
7
|
+
([RDQ18](docs/models/rdq18.md), [RDQ20-MF](docs/models/rdq20mf.md), [Land2017](docs/models/land2017.md),
|
|
8
|
+
[Lewalle2024](docs/models/lewalle2024.md)), all sharing a common interface (see "Choosing a Model"
|
|
9
|
+
below) so that a coupled electromechanics simulation can swap between them with minimal code
|
|
10
|
+
changes. See [docs/models](docs/models/index.md) for a description and reference for each model.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
The package requires Python 3.11+. You can install the base package and its dependencies using `pip`.
|
|
15
|
+
|
|
16
|
+
To install the library from the source code:
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/ComputationalPhysiology/crossbridge.git
|
|
19
|
+
cd crossbridge
|
|
20
|
+
pip install .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
To install with optional dependencies (for running demos, tests, or building docs):
|
|
24
|
+
```bash
|
|
25
|
+
pip install ".[demos]" # Installs scipy, matplotlib, gotranx, numba, zero-mech, etc.
|
|
26
|
+
pip install ".[test]" # Installs pytest and coverage tools
|
|
27
|
+
pip install ".[docs]" # Installs jupyter-book and sphinx plugins
|
|
28
|
+
pip install ".[all]" # Installs everything
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Basic Usage
|
|
32
|
+
To most basic usage its to solve for a single cell. The `RDQ18` class provides an `advance_ODE` method that takes in the time step, calcium concentration, and sarcomere length to update the internal state of the model. This can then be used to compute an active tension based on the fraction of permissive crossbridges.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import numpy as np
|
|
36
|
+
from crossbridge import RDQ18, calcium_trace, sl_trace
|
|
37
|
+
|
|
38
|
+
# Initialize a model with 1000 cells/integration points
|
|
39
|
+
num_cells = 1
|
|
40
|
+
dt = 0.01
|
|
41
|
+
dt_sarc = 2.5e-5
|
|
42
|
+
sarcomere = RDQ18(num_cells=num_cells, Ta_max=60.0, params={"dt": dt_sarc})
|
|
43
|
+
|
|
44
|
+
# Inputs
|
|
45
|
+
t = np.arange(0, 1, dt) # Time array [s]
|
|
46
|
+
Ca = calcium_trace(t) # Calcium transient [uM]
|
|
47
|
+
SL = sl_trace(t) # Sarcomere length transient [um]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
Ta = np.zeros(len(t))
|
|
51
|
+
# Advance the ODEs by one time step
|
|
52
|
+
for i, (Cai, SLi) in enumerate(zip(Ca, SL)):
|
|
53
|
+
sarcomere.advance_ODE(dt, Cai, np.array([SLi]))
|
|
54
|
+
|
|
55
|
+
# Compute the fraction of permissive crossbridges (proxy for active tension)
|
|
56
|
+
permissivity = sarcomere.compute_permissivity()[0]
|
|
57
|
+
active_tension = sarcomere.Ta_max * permissivity
|
|
58
|
+
Ta[i] = active_tension
|
|
59
|
+
|
|
60
|
+
import matplotlib.pyplot as plt
|
|
61
|
+
|
|
62
|
+
fig, ax = plt.subplots(3, 1, sharex=True, figsize=(8, 6))
|
|
63
|
+
ax[0].plot(t, Ca)
|
|
64
|
+
ax[0].set_ylabel("Calcium [uM]")
|
|
65
|
+
ax[1].plot(t, SL)
|
|
66
|
+
ax[1].set_ylabel("Sarcomere Length [um]")
|
|
67
|
+
ax[2].plot(t, Ta)
|
|
68
|
+
ax[2].set_ylabel("Active Tension [kPa]")
|
|
69
|
+
ax[2].set_xlabel("Time [s]")
|
|
70
|
+
fig.tight_layout()
|
|
71
|
+
plt.show()
|
|
72
|
+
```
|
|
73
|
+

|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## Coupling to Electrophysiology and Mechanics
|
|
77
|
+
Most cellular and tissue-level simulations will require coupling the `RDQ18` model to electrophysiology and mechanics. The `advance_ODE` method is designed to be called at every time step of a larger simulation loop, allowing the sarcomere dynamics to evolve in response to changing calcium and length conditions.
|
|
78
|
+
|
|
79
|
+
The calcium concentration can be either a scalar (if all cells/integration points are assumed to have the same calcium transient) or an array with one entry per cell/integration point. The sarcomere length can similarly be a scalar or an array. Below is a pseudo-code example of how this coupling might look in a larger simulation loop:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
SL = np.full(num_cells, 2.2) # Initial sarcomere length [um]
|
|
85
|
+
for t in time_steps:
|
|
86
|
+
# Compute calcium from electrophysiology model
|
|
87
|
+
Cai = compute_calcium(t)
|
|
88
|
+
sarcomere.advance_ODE(dt, Cai, SL)
|
|
89
|
+
# Compute active tension and update mechanics
|
|
90
|
+
permissivity = sarcomere.compute_permissivity()[0]
|
|
91
|
+
active_tension = sarcomere.Ta_max * permissivity
|
|
92
|
+
# Update mechanics model with new active tension
|
|
93
|
+
# and compute new sarcomere length (SL) based on
|
|
94
|
+
# the mechanical response of the tissue
|
|
95
|
+
SL = compute_new_length(active_tension, SL)
|
|
96
|
+
...
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Examples & Demos
|
|
100
|
+
|
|
101
|
+
The `demo/` folder contains several scripts demonstrating how to couple the `crossbridge` model to different physics scales. See [demos](demo/index.md) for a description of each demo and how to run them.
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
## Testing and Development
|
|
105
|
+
We use `pytest` for unit testing. To run the test suite and check code coverage:
|
|
106
|
+
```bash
|
|
107
|
+
pytest
|
|
108
|
+
```
|
|
109
|
+
To run the pre-commit linters (Ruff and MyPy):
|
|
110
|
+
```bash
|
|
111
|
+
pre-commit run --all-files
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
[build-system] # Require setuptool version due to https://github.com/pypa/setuptools/issues/2938
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
|
|
4
|
+
[project]
|
|
5
|
+
name = "crossbridge"
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
description = "Cardiac crossbridge models"
|
|
8
|
+
authors = [{ name = "Henrik Finsberg", email = "henriknf@simula.no" }]
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"numpy",
|
|
14
|
+
"scipy"
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.11"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
test = ["pytest", "pytest-cov"]
|
|
21
|
+
demos = ["matplotlib", "zero-mech", "gotranx", "scipy", "tqdm", "numba"]
|
|
22
|
+
docs = [
|
|
23
|
+
"jupyter-book<2.0",
|
|
24
|
+
"jupytext",
|
|
25
|
+
"crossbridge[demos]",
|
|
26
|
+
"sphinxcontrib-bibtex",
|
|
27
|
+
]
|
|
28
|
+
all = [
|
|
29
|
+
"crossbridge[test]",
|
|
30
|
+
"crossbridge[demos]",
|
|
31
|
+
"crossbridge[docs]",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
[tool.pytest.ini_options]
|
|
39
|
+
addopts = [
|
|
40
|
+
"--cov=crossbridge",
|
|
41
|
+
"--cov-report=html",
|
|
42
|
+
"--cov-report=term-missing",
|
|
43
|
+
"-v",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
|
|
48
|
+
[tool.mypy]
|
|
49
|
+
files = ["src/crossbridge", "tests"]
|
|
50
|
+
ignore_missing_imports = true
|
|
51
|
+
exclude = [
|
|
52
|
+
"docs",
|
|
53
|
+
"examples",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
[tool.ruff]
|
|
59
|
+
|
|
60
|
+
# Exclude a variety of commonly ignored directories.
|
|
61
|
+
exclude = [
|
|
62
|
+
"examples",
|
|
63
|
+
".bzr",
|
|
64
|
+
".direnv",
|
|
65
|
+
".eggs",
|
|
66
|
+
".git",
|
|
67
|
+
".hg",
|
|
68
|
+
".mypy_cache",
|
|
69
|
+
".nox",
|
|
70
|
+
".pants.d",
|
|
71
|
+
".pytype",
|
|
72
|
+
".ruff_cache",
|
|
73
|
+
".svn",
|
|
74
|
+
".tox",
|
|
75
|
+
".venv",
|
|
76
|
+
"__pypackages__",
|
|
77
|
+
"_build",
|
|
78
|
+
"buck-out",
|
|
79
|
+
"build",
|
|
80
|
+
"dist",
|
|
81
|
+
"node_modules",
|
|
82
|
+
"venv",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
# Same as Black.
|
|
86
|
+
line-length = 100
|
|
87
|
+
|
|
88
|
+
# Assume Python 3.12.
|
|
89
|
+
target-version = "py312"
|
|
90
|
+
|
|
91
|
+
[tool.ruff.lint]
|
|
92
|
+
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
|
|
93
|
+
select = ["E", "F"]
|
|
94
|
+
ignore = ["E402", "E741", "E743", "E731"]
|
|
95
|
+
|
|
96
|
+
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
|
97
|
+
fixable = ["A", "B", "C", "D", "E", "F"]
|
|
98
|
+
unfixable = []
|
|
99
|
+
|
|
100
|
+
# Allow unused variables when underscore-prefixed.
|
|
101
|
+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
[tool.ruff.lint.mccabe]
|
|
105
|
+
# Unlike Flake8, default to a complexity level of 10.
|
|
106
|
+
max-complexity = 10
|
|
107
|
+
|
|
108
|
+
[tool.ruff.lint.per-file-ignores]
|
|
109
|
+
# Ignore "Line too long" (E501) for all files in the specific folder
|
|
110
|
+
"demo/*" = ["E501"]
|
|
111
|
+
|
|
112
|
+
[tool.bumpversion]
|
|
113
|
+
allow_dirty = false
|
|
114
|
+
commit = true
|
|
115
|
+
message = "Bump version: {current_version} → {new_version}"
|
|
116
|
+
tag = true
|
|
117
|
+
sign_tags = false
|
|
118
|
+
tag_name = "v{new_version}"
|
|
119
|
+
tag_message = "Bump version: {current_version} → {new_version}"
|
|
120
|
+
current_version = "0.1.0"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
[[tool.bumpversion.files]]
|
|
124
|
+
filename = "pyproject.toml"
|
|
125
|
+
search = 'version = "{current_version}"'
|
|
126
|
+
replace = 'version = "{new_version}"'
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from .base import CardiacActivationModel
|
|
2
|
+
from .rdq18 import RDQ18
|
|
3
|
+
from .rdq20mf import RDQ20MF
|
|
4
|
+
from .lewalle2024 import Lewalle2024
|
|
5
|
+
from .land17 import Land2017
|
|
6
|
+
from . import utils
|
|
7
|
+
from .utils import calcium_trace, sl_trace
|
|
8
|
+
|
|
9
|
+
#: Maps a short model name to its class, so a model can be selected by name
|
|
10
|
+
#: (e.g. from a config file) instead of importing the class directly. Every
|
|
11
|
+
#: model here is a `CardiacActivationModel` and can be constructed uniformly
|
|
12
|
+
#: as `ModelClass(num_cells, Ta_max, params)`, so switching between them
|
|
13
|
+
#: requires no other code changes beyond the name/class used.
|
|
14
|
+
MODEL_REGISTRY: dict[str, type[CardiacActivationModel]] = {
|
|
15
|
+
"RDQ18": RDQ18,
|
|
16
|
+
"RDQ20MF": RDQ20MF,
|
|
17
|
+
"Lewalle2024": Lewalle2024,
|
|
18
|
+
"Land2017": Land2017,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_model(name: str) -> type[CardiacActivationModel]:
|
|
23
|
+
"""
|
|
24
|
+
Look up a `CardiacActivationModel` subclass by name.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
name : str
|
|
29
|
+
One of the keys in `MODEL_REGISTRY` (e.g. "RDQ18", "RDQ20MF",
|
|
30
|
+
"Lewalle2024").
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
type[CardiacActivationModel]
|
|
35
|
+
The model class, ready to be instantiated as
|
|
36
|
+
`get_model(name)(num_cells, Ta_max, params)`.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
return MODEL_REGISTRY[name]
|
|
40
|
+
except KeyError:
|
|
41
|
+
available = ", ".join(sorted(MODEL_REGISTRY))
|
|
42
|
+
raise KeyError(f"Unknown model {name!r}. Available models: {available}") from None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"CardiacActivationModel",
|
|
47
|
+
"RDQ18",
|
|
48
|
+
"RDQ20MF",
|
|
49
|
+
"Lewalle2024",
|
|
50
|
+
"Land2017",
|
|
51
|
+
"MODEL_REGISTRY",
|
|
52
|
+
"get_model",
|
|
53
|
+
"utils",
|
|
54
|
+
"calcium_trace",
|
|
55
|
+
"sl_trace",
|
|
56
|
+
]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import numpy as np
|
|
3
|
+
import numpy.typing as npt
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CardiacActivationModel(ABC):
|
|
7
|
+
"""
|
|
8
|
+
Abstract base class for all reduced-order cardiac activation models.
|
|
9
|
+
Designed to support vectorized execution across multiple cells or
|
|
10
|
+
integration points.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def __init__(self, num_cells: int, Ta_max: float, params: dict | None = None):
|
|
15
|
+
"""
|
|
16
|
+
Initialize the model state and precompute necessary constants.
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
-----------
|
|
20
|
+
num_cells : int
|
|
21
|
+
The number of independent spatial units to simulate simultaneously.
|
|
22
|
+
Ta_max : float
|
|
23
|
+
The maximum active tension scaling factor.
|
|
24
|
+
params : dict, optional
|
|
25
|
+
Model-specific parameters to override defaults.
|
|
26
|
+
"""
|
|
27
|
+
self.num_cells = num_cells
|
|
28
|
+
self.Ta_max = Ta_max
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def default_parameters(cls) -> dict:
|
|
33
|
+
"""
|
|
34
|
+
Return a dictionary of the default physiological parameters for the model.
|
|
35
|
+
"""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def advance_step(
|
|
40
|
+
self,
|
|
41
|
+
dt: float,
|
|
42
|
+
Ca_val: float | npt.NDArray[np.float64],
|
|
43
|
+
SL_vals: float | npt.NDArray[np.float64],
|
|
44
|
+
dSL_vals: float | npt.NDArray[np.float64] | None = None,
|
|
45
|
+
) -> None:
|
|
46
|
+
"""
|
|
47
|
+
Integrate the model's internal states forward by a single time step.
|
|
48
|
+
|
|
49
|
+
Parameters:
|
|
50
|
+
-----------
|
|
51
|
+
dt : float
|
|
52
|
+
The time step size in seconds.
|
|
53
|
+
Ca_val : float or np.ndarray
|
|
54
|
+
Intracellular calcium concentration.
|
|
55
|
+
SL_vals : float or np.ndarray
|
|
56
|
+
Current sarcomere length(s).
|
|
57
|
+
dSL_vals : float or np.ndarray, optional
|
|
58
|
+
Current sarcomere shortening velocity. Defaults to 0 if not provided.
|
|
59
|
+
"""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def get_active_tension(self) -> npt.NDArray[np.float64]:
|
|
64
|
+
"""
|
|
65
|
+
Compute and return the macroscopic active tension (Ta) generated.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
--------
|
|
69
|
+
np.ndarray
|
|
70
|
+
The active tension for each cell/integration point (shape: `num_cells`).
|
|
71
|
+
"""
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def reset(self) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Reset the model's internal state to its initial condition (as set by
|
|
78
|
+
`__init__`), without re-allocating precomputed constants.
|
|
79
|
+
"""
|
|
80
|
+
pass
|