sknm
A Python implementation of the Simplified Kirchhoff Network Model (SKNM) of Jæger & Tveito, Scientific Reports 13:16434 (2023), together with KNM and SKNM(uₑ=0) so that the paper's model comparisons can be reproduced.
The domain is cell-based cardiac and islet electrophysiology: excitable cells coupled through gap
junctions, resolved per cell rather than homogenized into a continuum. See
CONTEXT.md for the vocabulary this codebase uses.
Status: early development. The model, the solvers and the analysis are implemented, and the scripts in
examples/reproduce the paper's figures for both of its cell types, hiPSC-derived cardiomyocytes and pancreatic β cells. The homogenized bidomain and monodomain comparisons (Figures 6 to 9) are reproduced too, throughdolfinxandfenicsx-beatrather than insknmitself: those four scripts need both installed and report what is missing when they are not.
Installation
python -m pip install sknm
Runtime dependencies are numpy, scipy and pint. matplotlib arrives with the examples extra,
and gotranx with the codegen extra, which is needed only to regenerate the membrane models
(the generated Python is committed).
Two membrane models ship: base_model_IM, an hiPSC-CM model, and PBM, the phantom bursting
model of the pancreatic β cell. Both are generated from the .ode sources in the repository
root and checked against the authors' C++ elementwise.
Usage
Every dimensional argument carries a unit; a bare number is refused. This reproduces the conduction velocity of Table S1 of the supplementary, 3.73 cm/s, in about two seconds:
from sknm import Simulation, Variant, analysis, presets
from sknm.membrane import base_model_IM, from_gotranx
from sknm.units import ms, mV
network = presets.hipsc_sheet(40, 40, alpha=1.0)
model = from_gotranx(base_model_IM)
sim = Simulation(network, model, variant=Variant.SKNM, dt=0.02 * ms)
sim.set_parameter("stim_amplitude", presets.hipsc_stimulus_amplitude(40, 40))
path = presets.hipsc_conduction_path(40, 40, alpha=1.0)
recorder = analysis.ActivationRecorder(sim, threshold=-20 * mV, stop_when_activated=path.end)
sim.run(50 * ms, record=(), callback=recorder)
print(analysis.conduction_velocity(recorder, path))
print(recorder.max_upstroke_velocity[presets.hipsc_centre_cell(40, 40)], "V/s")
Run the same network as Variant.KNM to solve for the extracellular potential as well.
The β cell setup is the same shape, through presets.beta_*, and reproduces Table S4's
0.0243 cm/s:
from sknm import Simulation, Variant, analysis, presets
from sknm.units import ms
network = presets.beta_sheet(15, 15)
sim = Simulation(network, presets.beta_membrane_model(), variant=Variant.SKNM, dt=0.02 * ms)
sim.set_parameter("gkatpbar", presets.beta_stimulus_conductance(15, 15))
path = presets.beta_conduction_path(15, 15)
recorder = analysis.ActivationRecorder(
sim, threshold=presets.BETA_THRESHOLD, stop_when_activated=path.end
)
sim.run(1000 * ms, record=(), callback=recorder)
print(analysis.conduction_velocity(recorder, path))
ActivationRecorder has no default threshold. The two cell types activate at −20 mV and
−50 mV, and a β action potential peaks at about −19.5 mV, so the cardiac threshold applied to a
β sheet leaves the measurement cell unactivated. presets.HIPSC_THRESHOLD and
presets.BETA_THRESHOLD are the two values.
Examples
Each script in examples/ reproduces one of the paper's figures, writing it to
examples/figures/ and printing the numbers it plotted.
| Script | Reproduces |
|---|---|
fig02_travelling_wave.py |
Figure 2: snapshots of a wave crossing 40×40 cells, KNM against SKNM |
fig03_anisotropy.py |
Figure 3: conduction velocity against cell length-to-width ratio |
fig04_gap_junction_variation.py |
Figures 4 and S2: conduction velocity against gap junction variation |
fig05_sources_of_difference.py |
Figure 5: the two factors that make KNM and SKNM differ |
fig10_beta_travelling_wave.py |
Figure 10: snapshots of a wave crossing 15×15 β cells |
fig11_beta_variable_coupling.py |
Figure 11: the same, at γ = 1 and 2% extracellular volume |
fig12_beta_gap_junction_variation.py |
Figures 12 and S1: β conduction velocity against gap junction variation |
python -m pip install -e ".[examples]"
python examples/fig03_anisotropy.py # a reduced sample, about a minute
SKNM_EXAMPLES_FULL=1 python examples/fig03_anisotropy.py # the paper's whole sample
Every script is fast by default: it sweeps every other point of its parameter range unless
SKNM_EXAMPLES_FULL is set. The reduction drops points and never makes a point cheaper. Each
point plotted is computed on the paper's own 40×40 sheet at its own 0.02 ms time step, so a full
run adds markers rather than moving them. Results are cached under examples/results/ and
reused, so restyling a figure costs no simulation. The cache is keyed on the parameters of a run
and cannot see that sknm itself has changed, so set SKNM_EXAMPLES_NO_CACHE=1 or delete the
directory after changing the library.
Each script is also a page of the documentation site, so it is run both as a script and, by the
docs build, as a notebook. A notebook's process belongs to the Jupyter kernel and its command
line describes the kernel rather than the script, so these are environment variables rather than
options: SKNM_EXAMPLES_FULL, SKNM_EXAMPLES_NO_CACHE and SKNM_EXAMPLES_OUTPUT_DIR.
The gap junction draws differ between the two cell types, and the difference is measurable. The
cardiac figures seed a generator rather than using the paper's own random numbers: on a 40×40
sheet with a 25-cell conduction path the choice of draws moves a velocity by about 2%, so those
curves sit very close to the published ones rather than on top of them. The authors' draws can be
passed to presets.vary_conductances instead, if you have them.
The β figures cannot do that. A 15×15 sheet has 420 connections and an 8-cell path, so at γ = 1
the draws move the velocity by 20%, and every seed tried fell below the axis of the published
Figure S1. Those scripts therefore read the authors' own 420 draws, committed with their
provenance under examples/data/. No number the test suite asserts depends on
them: every published target is at γ = 0, where the draws cancel out of the formula.
Development
pytest # tests
pre-commit run --all-files # ruff lint, ruff format, mypy
Requires Python 3.11 or newer.
Attribution
This is an independent Python reimplementation. It is not the authors' code and is not endorsed by them. The model, its equations and its reference parameters are due to:
Jæger, K.H., Tveito, A. The simplified Kirchhoff network model (SKNM): a cell-based reaction–diffusion model of excitable tissue. Scientific Reports 13, 16434 (2023). https://doi.org/10.1038/s41598-023-43444-9
The authors' own C++ implementation (found https://doi.org/10.5281/zenodo.8340201) is distributed under CC-BY-4.0 and was used as the ground
truth for every numerical choice made here. Please cite the paper above in any work that uses this
package; see CITATION.cff.
The paper above is derrived from the paper
Jæger, K. H., & Tveito, A. (2023). *Efficient, cell-based simulations of cardiac electrophysiology; the Kirchhoff Network Model (KNM). NPJ systems biology and applications, 9(1), 25. https://doi.org/10.1038/s41540-023-00288-3
with it's implementation in C++ found at https://doi.org/10.5281/zenodo.7848664.
Licence
MIT. See LICENSE.
Metadata
Release files for sknm 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sknm-0.1.1.tar.gz | 503.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sknm-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 569.6 kB
Release files / sknm-0.1.1.tar.gz
| Download URL | sknm-0.1.1.tar.gz |
|---|---|
| Size | 503.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f8442e468cd5b4817d06dc9414e1cf0cb5a8b1c8ffacc41c4a6ba0556d1015f0
|
|
BLAKE2b-256 checksum How to use checksums |
ae8dc04fee113d880da8f41f5da62978be8596271912b7ca3f754e21fe24c9fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / sknm-0.1.1-py3-none-any.whl
| Download URL | sknm-0.1.1-py3-none-any.whl |
|---|---|
| Size | 66.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eb574ac5c589400491e7ea06e9a766eef456e5886f214006f297f823ae917935
|
|
BLAKE2b-256 checksum How to use checksums |
2bdefeb649230ee35d584f778b0754fb043d513b12b31f6c03027161e8ee673c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency log