Featured image of post Pigreads: The Python-integrated GPU-enabled reaction-diffusion solver using OpenCL for cardiac electrophysiology and other applications

Pigreads: The Python-integrated GPU-enabled reaction-diffusion solver using OpenCL for cardiac electrophysiology and other applications

Scientific article about a solo-designed code which was built over approximately one year, with a working prototype producing correct results within weeks. Pigreads simulates cardiac electrophysiology and other reaction-diffusion systems on GPU hardware using OpenCL, and can also run on CPU. Designed around a keep-it-simple philosophy: a lean, maintainable codebase that integrates naturally into the Python ecosystem.

This article was previously published in Computer Physics Communications, 110088 (Kabus, et al., 2026). More about Pigreads can be found online at its code repository on GitLab, its documentation. A PDF of the article can be downloaded at the website of Computer Physics Communications 110088 (DOI: 10.1016/j.cpc.2026.110088), or directly here.

Authors:

Institutions:

  1. Laboratory of Experimental Cardiology, Leiden University Medical Center (LUMC), Albinusdreef 2, 2333 ZA Leiden, The Netherlands

  2. Mathematical Institute, Leiden University, Einsteinweg 55, 2333 CC Leiden, The Netherlands

DOI: 10.1016/j.cpc.2026.110088

Keywords:

  • reaction-diffusion
  • finite differences
  • Python
  • OpenCL
  • GPU
  • electrophysiology

Highlights:

  • GPU-accelerated reaction-diffusion solver in up to three dimensions.
  • Minimal NumPy-friendly API with OpenCL kernels.
  • Includes commonly used reaction terms for cardiac electrophysiology.
  • Open-source, tested, documented, with examples and tutorials.

Abstract:

Pigreads is a streamlined Python module for efficient numerical solution of reaction-diffusion systems on graphics cards (GPU), with CPU fallback. It exposes a simple and straight-forward NumPy-compatible API. Users may employ built-in models – including electrophysiology examples – or supply custom reaction terms. Supported features include 0D-3D uniform Cartesian grids, no-flux and periodic boundary conditions, anisotropic diffusion, spatially varying diffusion and reaction, and localised source terms. The project is open-source, tested, documented, and distributed with examples and tutorials.

Introduction

Reaction-diffusion systems appear across physics, chemistry, biology, and ecology; they are used to study pattern formation and excitable waves, for instance in electrophysiology (Cannon et al., 2014; Clayton et al., 2011; Kapral & Showalter, 1995; Lechleiter et al., 1991; Murray, 1976; Rotermund et al., 1990). Their numerical solution can be computationally demanding, so efficient implementations are essential, ranging from ad-hoc research codes to large general software packages (Niederer et al., 2011). However, modern applications such as inverse problems, parameter estimation, uncertainty quantification, and machine learning, require a compact code with minimal installation requirements that is portable across platforms, runs fast, and can easily be integrated into other software or frameworks.

Pigreads is a Python module (van Rossum et al., 1995) that provides a minimal, concise, and NumPy-friendly API (Harris et al., 2020) to define and run reaction-diffusion simulations while performing the costly computations efficiently on accelerators: Computational kernels implemented in OpenCL (Stone et al., 2010) run on both GPUs and CPUs as a fallback via PyOpenCL (Klöckner et al., 2012). The Pigreads module includes several pre-defined models relevant to electrophysiology; users can also supply custom reaction terms.

Representative examples are illustrated in Fig. 1: simulations of the conduction in a 1D cable of excitable cells, 2D pieces of cardiac tissue like monolayers in culture wells, and numerical electrophysiology experiments of whole organs in 3D like the ventricles or atria of the human heart. The 0D case, i.e., without diffusion, is also supported to perform single cell simulations.

Pigreads can be used to solve reaction-diffusion systems in up to three dimensions

Figure 1: Pigreads can be used to solve reaction-diffusion systems in up to three dimensions. A. Cardiac action potentials for the model by Courtemanche et al. (1998) in the 0D case, i.e., without diffusion. B. Three travelling pulses in a cable (1D) followed by a fourth blocked pulse in the same model. C. A spiral wave in a circular 2D domain in the model by Marcotte & Grigoriev (2017) that was stimulated by increasing the main variable on a line between high and low values of the recovery variable. D. Pseudo-electrograms for a travelling wave in a 2D piece of tissue with periodic boundary conditions on the top and bottom and different cell types for mid-wall, endo-, and epi-cardial cells following the model by ten Tusscher & Panfilov (2006) with 15% fibrosis, stimulated three pulses at the locations and times labelled S0–2. E. Three-dimensional cardiac electrophysiology simulations in human bi-ventricular (Hren, 1996, 1998; Keldermann et al., 2009) and human bi-atrial (De Coster et al., 2018; Dossel et al., 2011) geometries with faster diffusion along muscle fibres. A single pulse is sent out from the apex or the sino-atrial node, respectively. We use the models by ten Tusscher & Panfilov (2006) for the ventricles and by Courtemanche et al. (1998) for the atria.

The module is available on GitLab, where it is properly tested using continuous integration, and is well-documented on Read the Docs, with examples and tutorials. Pigreads can be installed from the Python Package Index PyPI via:

$ pip install pigreads

Consider also installing the optional dependencies for the command line interface (CLI) and creating plots and videos via:

$ pip install 'pigreads[all]'

The CLI can be used to run and visualise Pigreads simulations defined in a YAML file (Net, 2023) adhering to a Pydantic scheme as described in the documentation.

Mathematical problem definition

We generally define reaction-diffusion systems as: $$ \partial_t {{\underline{{u}}}}(t, {{\bm{{x}}}}) = \underbrace{ {{\underline{{P}}}} \nabla \cdot {{\bm{{D}}}}({{\bm{{x}}}}) \nabla {{\underline{{u}}}}(t, {{\bm{{x}}}}) }_{\text{diffusion}} + \underbrace{ {{\underline{{r}}}}({{\underline{{u}}}}, {{\bm{{x}}}}) }_{\text{reaction}} + \underbrace{ {{\underline{{s}}}}(t, {{\bm{{x}}}}) }_{\text{source}} \qquad{(1)}$$ for time $t\in[0, T]$ and space ${{\bm{{x}}}} = {{{{\left[ x, y, z \right]}}}^\mathrm{T}} \in\heartsuit\subset\mathbb R^3$ in the domain $\heartsuit$, with initial conditions ${{\underline{{u}}}}(0, {{\bm{{x}}}})$ and no-flux boundary conditions $0 = {{\bm{{n}}}}\cdot {{\bm{{D}}}} \nabla u$ for diffused variables on ${{\bm{{x}}}} \in \partial\heartsuit$. At each point in time and space, the states vector ${{\underline{{u}}}}(t, {{\bm{{x}}}}) \in \mathbb R^{\mathtt{Nv}}$ consists of Nv elements, the so-called states or variables, $u_0$, $u_1$, …, $u_{\mathtt{Nv}-1}$. For the diffusion term, we define the diffusivity matrix ${{\bm{{D}}}}({{\bm{{x}}}}) \in \mathbb R^{3\times 3}$ and the selection matrix ${{\underline{{P}}}} \in \mathbb R^{\mathtt{Nv}\times\mathtt{Nv}}$ to select which variables to diffuse and how strongly. The selection matrix often takes the sparse form ${{\underline{{P}}}} = \operatorname{diag}(P_{0}, ..., P_{\mathtt{Nv}-1})$ with mostly zeros. The reaction term ${{\underline{{r}}}}({{\underline{{u}}}}, {{\bm{{x}}}}) \in \mathbb R^{\mathtt{Nv}}$ describes the local dynamics of the system and may vary in space, for instance in parameters or even in the model equations themselves. We refer to a specific local choice of ${{\underline{{r}}}}_{\mathtt{imodel}}({{\underline{{u}}}})$ with fixed parameters as a model; ${{\underline{{r}}}}$ refers to the reaction term, i.e., all models. The source term ${{\underline{{s}}}}(t, {{\bm{{x}}}}) \in \mathbb R^{\mathtt{Nv}}$ can be used to add external influences to the system, for instance to stimulate the system at specific times and locations.

For no source term, ${{\underline{{s}}}} = 0$, homogeneous and isotropic diffusion, ${{\bm{{D}}}}({{\bm{{x}}}}) = D = \text{const.}$, and only two variables, $u = u_0$ and $v = u_1$, with only $u$ diffusing, $P_0 = 1$, $P_1 = 0$, the system reduces to: $$ \begin{align} \partial_t u(t, {{\bm{{x}}}}) &= D \nabla^2 u(t, {{\bm{{x}}}}) + r_u(u, v) \\ \partial_t v(t, {{\bm{{x}}}}) &= r_v(u, v) \end{align} \qquad{(2)}$$

Technical overview

Pigreads organises simulation components around three objects: an instance of the class Models that defines local reaction terms, an array inhom that defines the domain and which model to use where, and an array states holding all state variables on a uniform Cartesian grid; see also the flowchart in Fig. 2.

Main components of a simulation to solve a reaction-diffusion problem using Pigreads

Figure 2: Main components of a simulation to solve a reaction-diffusion problem using Pigreads. The state variables at each point in the grid at the frames are stored in the variable states. The main computations are done in the method models.run, which performs Nt forward Euler steps with time step dt, outputting the subsequent frame after these Nt steps.

Numerical choices are deliberately simple: finite-difference stencils for spatial derivatives and explicit forward-Euler time stepping. Supported capabilities include 0D-3D grids, no-flux and periodic boundary conditions, spatially varying anisotropic diffusivity and reaction terms, and source terms.

Diffusion and reaction kernels run in OpenCL on the selected GPU/CPU device; Python performs setup, parameter changes, and in- and output while results remain accessible as NumPy arrays for analysis and plotting, for instance using Matplotlib (Hunter, 2007) or PyVista (Sullivan & Kaszynski, 2019).

Note that execution of the kernels requires a compatible OpenCL runtime environment. For example, for Nvidia graphics cards, the runtime is provided through the CUDA toolkit and corresponding device drivers. For AMD devices, it is available via the AMD ROCm platform, and for Intel devices, through the Intel oneAPI toolkit. Alternatively, the PoCL implementation can be used for CPU-based or generic OpenCL execution.

We validated our code by using it to successfully compute the correct solution to the problem proposed by Niederer et al. (2011): simulation of the model by ten Tusscher & Panfilov (2006) for 100 ms in a slab of 3 × 7 × 20 mm. As a benchmark, on an NVIDIA GeForce RTX 2080 Super GPU with Max-Q Design, the computations take 0.025 s at the coarsest resolution of 0.5 mm and 0.05 ms; and 41.9 s at the finest resolution of 0.1 mm and 0.005 ms.

Unit testing is used to ensure all components of Pigreads work as expected: Code changes are automatically tested using continuous integration where the API is exercised and numerically validated against known results. The tests cover 100% of the code.

Hands-on example

In this section, we define and run a simple 2D simulation of a spiral wave in a two-variable model that breaks up to spiral chaos in a circular domain with no-flux boundary conditions. With this example, it is shown in which steps a Pigreads simulation is usually defined.

First, define the coordinates of the geometry to be used. In this example, we use a 2D plane with 200 points in both $x$ and $y$:

import pigreads as pig
import numpy as np
R = 10
z, y, x = np.mgrid[0:1, -R:R:200j, -R:R:200j]
Nz, Ny, Nx = x.shape
dz, dy, dx = pig.deltas(z, y, x)

Pigreads is optimised for three-dimensional space. For lower-dimensional simulations, set the number of points in additional dimensions to one, as done above for the $z$-dimension. Note that np.mgrid is used to define a dense multi-dimensional grid where the coordinates, in this case, are defined as the range of integers from 0 up to but excluding 1 in z, i.e., $z \in \{0\}$, and -R to R divided into 200 equal steps including both end points in y and x. See the documentation of NumPy for details. The function pig.deltas returns the grid spacing dz, dy, dx that is used in the arrays z, y, x, which each have a shape of (Nz, Ny, Nx). Space in Pigreads is periodic by default, such that the points at index ix = 0 are neighbours with the points at ix = 1 and ix = Nx - 1, and likewise for the other dimensions.

The grid spacing dz, dy, dx must be chosen small enough to resolve the dynamics of the system, but large enough to keep the computational cost manageable. For instance, at insufficient spatial resolution, the conduction is slower along grid axes than diagonal to them. One may use this effect to determine an as large as possible grid spacing dx such that the difference between the fastest and slowest stimulation at some distance is less than a given threshold, for instance 5%.

The integer field inhom defines which points are outside (inhom == 0) and inside the domain $\heartsuit$ (inhom > 0). Pigreads implements no-flux boundary conditions on $\partial\heartsuit$. In our example, we define the domain $\heartsuit$ as a disk of radius $R$:

inhom = np.ones(x.shape, dtype=int)
r = np.linalg.norm((x, y, z), axis=0)
inhom[r >= R] = 0

Values of inhom larger than zero can be used to select one or multiple models, i.e., reaction terms ${{\underline{{r}}}}$. For an inhom value of 1, models[0] is used; and models[1] for a value of 2, etc. One or more of the available models can be selected using an instance of the Models class:

models = pig.Models()
models.add("marcotte2017dynamical", beta=1.389)
Nv = models.Nv

The key of a model – an identifying string – is here used to choose a model and keyword arguments are used to set model parameters to different values than the defaults; the function add may be called multiple times to add more models. When using multiple models, the largest number of variables Nv is used.

Various models are pre-defined in Pigreads, and defining another model is straight-forward, see section 5. A list of the available models can also be obtained programmatically using:

for key in pig.Models.available.keys():
    print(key)
# aliev1996simple
# barkley1991model
# beeler1977reconstruction
# ...

For some models, there are multiple sets of parameters available, they may be accessed via the additional meta data of a model definition:

key = "tentusscher2006alternans"
model_def = pig.Models.available[key]
endo = model_def.meta["parameter sets"]["endo"]
for parameter, value in endo.items():
    print(f"{parameter} = {value}")
# g_Ks = 0.392
# g_to = 0.073
# s_offset = 28.0
# s_variant = 1.0
models_ = pig.Models()
models_.add(key, **endo)

In the next step, memory needs to be allocated to store the state variables of the model. For this, we typically use a 5D array of shape (Nfr, Nz, Ny, Nx, Nv) called states in Pigreads. It consists of the values of all Nv variables at each point in space on Nfr so-called frames in time.

One way to initialise a states array is using the function models.resting_states: It creates an array of the correct shape and fills its first frame with the appropriate resting values for each model according to the value of inhom:

Nfr = 100
states = models.resting_states(inhom, Nframes=Nfr)
assert states.shape == (Nfr, Nz, Ny, Nx, Nv)

Note that this allocates a potentially large amount of memory, as states is a 5D array of shape (Nfr, Nz, Ny, Nx, Nv). For large simulations, it may be better to only store a few frames and overwrite older frames, or to save frames to disk and not keep them in memory, for instance via memory mapping:

states = np.lib.format.open_memmap("states.npy",
    mode='w+', dtype=np.float32,
    shape=(Nfr, Nz, Ny, Nx, Nv),
)
states[:1] = models.resting_states(inhom, Nframes=1)

If the full history over time does not need to be kept, it is also possible to create a states array of only a few frames, continually overwriting them in an infinite loop. We use this scheme in some of the interactive examples that can be found in the code repository.

Initial conditions can then be set in the frame with ifr = 0:

states[0, x < -8, 0] = 1
states[0, y < 0, 1] = 2

Or equivalently expressed in mathematical notation: $$ \begin{align} u_0(t = 0, {{\bm{{x}}}}) &= \begin{cases} 1 & \text{if}\, x < -8 \\ u_{0,\,\text{rest}} & \text{else} \end{cases} \\ u_1(t = 0, {{\bm{{x}}}}) &= \begin{cases} 2 & \text{if}\, y < 0 \\ u_{1,\,\text{rest}} & \text{else} \end{cases} \end{align} \qquad{(3)}$$

The calculation of the diffusion term ${{\underline{{P}}}} \nabla \cdot {{\bm{{D}}}} \nabla {{\underline{{u}}}}$ is implemented as a weighted sum of neighbouring points. The weights can be calculated using the function weights, which also requires the diffusivity ${{\bm{{D}}}}$ as input:

diffusivity = pig.diffusivity_matrix(Df=0.03)
weights = models.weights(dz, dy, dx, inhom, diffusivity)

Calculating the weights is an expensive operation. Once they are calculated for a given geometry, it is possible to store and re-use them. Together with inhom, they fully encode all the necessary information about geometry needed for the numerical scheme in models.run below.

Finally, the simulation can be started using run to advance the simulation from one frame to the next, which is done in a loop over the number of frames Nfr. The function run does Nt forward Euler steps and only returns the final states after those steps:

Nt = 200
dt = 0.025
for ifr in range(Nfr - 1):
    states[ifr + 1] = models.run(
        inhom, weights, states[ifr],
        Nt=Nt, dt=dt,
    )

Note that for numerical stability, the time step dt needs to be chosen small enough to at least meet the Courant-Friedrichs-Lewy (CFL) condition (Courant et al., 1928), which informally can be summarised as: “The wave must travel less than one grid length per time step.” A smaller time step will lead to higher accuracy at higher computational cost. We usually choose dt as large as possible while still obtaining a solution that does not change significantly for smaller dt.

Additional arguments to run may be used to add stimulus currents at specific times and locations. See the documentation for more details.

Now that the simulation is done, the 5D array states containing the result can be analysed and visualised, for instance with Matplotlib (Hunter, 2007):

import matplotlib.pyplot as plt
plt.imshow(states[-1, 0, :, :, 0])
plt.show()

Or as a movie using FFmpeg:

from pigreads.plot import movie
movie("example.mp4", states[:, 0, :, :, 0])

Additional more in-depth examples are available in the comprehensive API documentation and in the Git repository of this project.

Reaction terms

The so-called models define the reaction term ${{\underline{{r}}}}$ of the reaction-diffusion equation (Eq. 1). While Pigreads comes with a variety of pre-defined models, it is also easily possible to define a model.

A model can be defined by adding it to the dictionary of available models using just a few lines of OpenCL code, for instance the model by FitzHugh (1961) and Nagumo et al. (1962):

import pigreads as pig
from pigreads.schema.model import ModelDefinition
pig.Models.available["fitzhugh1961impulses"] = \
ModelDefinition(
  name="FitzHugh 1961 & Nagumo 1962",
  description=
    "A 2D simplification of the Hodgkin-Huxley model.",
  dois=[
    "https://doi.org/10.1016/S0006-3495(61)86902-6",
    "https://doi.org/10.1109/JRPROC.1962.288235",
  ],
  variables={"u": 1.2, "v": -0.625},
  diffusivity={"u": 1.0},
  parameters={"a": 0.7, "b": 0.8, "c": 3.0, "z": 0.0},
  code="""
    *_new_u = u + dt * (v + u - u*u*u/3 + z + _diffuse_u);
    *_new_v = v + dt * (-(u - a + b*v)/c);
  """,
)

To import a model from a Myokit (Clerx et al., 2016) or CellML (Clerx et al., 2020) file, a converter is provided in the repository.

While the main application of Pigreads is cardiac electrophysiology, there are various more general pre-defined models: The trivial model defines diffusion of a single variable without a reaction term, the model by Gray & Scott (1983) is used in pattern formation studies, and the models by Barkley (1991) and Hodgkin & Huxley (1952) are some of the simplest and earliest models for electrophysiology in general. Simple example simulations for these models are shown in Fig. 3.

Pigreads includes various pre-defined models

Figure 3: Pigreads includes various pre-defined models. The panels each contain a 2D simulation of a different model and a time trace at the marked reference point in the domain for manually chosen simulation parameters. See also the model overview in Table ¿tbl:models?. A. The trivial model defines diffusion of a single variable. B. The model by Gray & Scott (1983) describes the formation of various patterns depending on chosen parameters. C. One of the simplest models with excitation and recovery enabling spiral waves is the model by Barkley (1991). D. The model by Hodgkin & Huxley (1952) of the electrical conduction in giant nerve fibre of squids is one of the first mathematical models of electrophysiology. Here, instead of a time trace, we show a simulation of a single cell.

An overview of the pre-defined models for cardiac electrophysiology is given in Fig. 4. The simulations shown in this figure have been performed using resolutions, timings and stimuli that, for each model, are found using an automatic procedure, as they depend on the model’s dynamics: time step dt, grid spacing dx, and the stimulus amplitude $\Delta u$ to be applied in a region by instantaneously increasing the first variable by the given value. In the remainder of this section, we outline this automatic procedure to find these parameters for a given cardiac electrophysiology model. Python scripts for this procedure are also published as a separate, second repository.

Models for cardiac electrophysiology that are pre-defined in Pigreads

Figure 4: Models for cardiac electrophysiology that are pre-defined in Pigreads. For each model, we present a the transmembrane voltage for a simulation of a single cell of three pulses with different intervals between the pulses and a 2D simulation of a spiral wave initiated with an S0-S1-S2 protocol. See also the model overview in Table ¿tbl:models?. A.–E. Phenomenological models. F.–J. Models for atrial cells. K.–O. Models for ventricular cells. Note: The spiral wave is not necessarily stable in all models and may break up into spiral chaos.

  1. First we must determine the time step dt and the stimulus $\Delta u$. We consider a large space of possible values $(\mathtt{dt}, \Delta u) \in S \subset [{10}^{-8}, 0) \times [{10}^{-5}, {10}^{5}]$, in which we run single-cell simulations (${{\bm{{D}}}} = 0$). We then narrow down this search space $S$ to the valid regime $S' \subset S$ of a single action potential. We do so by checking these criteria:

    • Are all variables finite at all points in time?
    • Does the first variable $u$ increase and decrease?
    • Does $u$ oscillate less than five times?
    • Does $u$ move away from the resting value?
    • Does $u$ excite past the stimulated value?
    • Does $u$ only change smoothly, i.e., without large jumps?
    • Does $u$ cover a large fraction of the observed range of it across simulations?

    We then pick dt at the 80th percentile of valid simulations $S'$, followed by $\Delta u$ at the 30th percentile of valid simulations at the chosen dt.

  2. We then run an additional single-cell simulation to determine the action potential duration (APD), i.e., the time that $u$ is above a 30% threshold.

  3. With a 1D simulation at a very fine grid spacing dx obtained via the CFL condition (Courant et al., 1928), we can then determine the conduction velocity (CV). Here, we for now set the diffusivity ${{\bm{{D}}}} = 1$ without loss of generality, as we can rescale it to any desired CV, as outlined below.

  4. We refine the values of CV and APD by measuring them for the third stimulus in 1D simulations with critical timings of stimulation.

  5. In a series of 2D simulations at ${{\bm{{D}}}} = 1$, we determine the largest grid spacing dx such that the difference between the fastest and slowest stimulation at some distance along the axes and diagonal to them is less than 5%.

  6. If necessary for numerical stability in 2D simulations, i.e., the CFL condition, we reduce dt.

The result of this automatic procedure is given in a tabular overview of all pre-defined models in Pigreads in Table ¿tbl:models?. This table includes references to the original publications of the models with their number of variables Nv, as well as the determined parameters for discretisation, dt and dx, the stimulus strength $\Delta u$, and resulting APD and CV at diffusivity ${{\bm{{D}}}} = 1$ in the units of the model. Note that these parameters can be scaled to a desired velocity via: $$ D \propto \mathrm{CV}^2, \quad \mathtt{dx} \propto \mathrm{CV}, \quad \mathtt{dt} = \text{const}. \qquad{(4)}$$ which follows for Eq. 1 from keeping the velocity and diffusivity the same in grid units, i.e., $D \cdot \mathtt{dt} / \mathtt{dx}^2$ and $\mathrm{CV} \cdot \mathtt{dt} / \mathtt{dx}$ are both constant.

In cardiac electrophysiology, stimuli in the form of instantaneous changes in variables, like the provided $\Delta u$, are considered less physically realistic than current-based stimuli which take the form of a source term ${{\underline{{s}}}}$ in Eq. 1. Usually, a suitable amplitude of a current-based stimulus can be obtained by spreading out the stimulus $\Delta u$ over some time $T$: $$ s_0 = \frac{\Delta u}{T} \qquad{(5)}$$

While the provided stimulus amplitude $\Delta u$ and resolutions in time and space work well in our tests, they may not be optimal for all applications. The user may want to adjust them for their specific use case.

Positioning across alternatives

Most solvers for cardiac electrophysiology have no or only partial GPU support; these codes utilise parallelisation on large numbers of CPU cores, for instance alphabetically: Alya Red (Vázquez et al., 2016), BeatBox (Antonioletti et al., 2017), CEPS (Leguèbe et al., 2025), cbcbeat (E. Rognes et al., 2017), Chaste (Pitt-Francis et al., 2009), Continuity (Gonzales et al., 2016), fenicsx-beat (H. Finsberg, 2025), FiniteWave (Nezlobinsky et al., 2026), Ithildin (Kabus et al., 2024), lifex-ep (Africa et al., 2023), openCARP (Plank et al., 2021), OpenCMISS (Bradley et al., 2011), simcardems (H. N. T. Finsberg et al., 2023), and svMultiPhysics (Updegrove et al., 2016).

The solvers that use GPUs vary in terms of the numerical scheme, backend, and language used for the API to set up numerical simulations: Abubu.js is a finite-differences code using WebGL which is accessed via JavaScript in a web browser (Kaboudian et al., 2019); CardioMat uses finite differences in the Matlab Parallel Computing Toolbox (Biasi et al., 2025); and MonoAlg3D is a finite-volumes solver using MPI and CUDA that is interfaced with using custom INI files containing parameters (Berg et al., 2025).

Python’s robust packaging system, scientific libraries for numerical computing, data visualisation, and machine learning, as well as its ease of use, make it an attractive choice for developing scientific software. There is another GPU-based cardiac electrophysiology solver developed around the same time as Pigreads that also integrates well into the Python ecosystem: TorchCor uses finite elements with the PyTorch deep learning library (Zhou et al., 2026). Pigreads fundamentally differs from this as it uses finite differences and OpenCL as the backend. Due to the simpler numerical scheme and use of optimised low-level OpenCL code, Pigreads is usually faster than TorchCor for the same problem sizes. Pigreads solves the benchmark problem proposed by Niederer et al. (2011) around ten times faster than TorchCor: On the same hardware, TorchCor computes the solution in 9.2 s for the coarsest resolution, and 264 s for the finest resolution. For Pigreads’ performance, see section 3.

Outlook

While we here have focused on cardiac electrophysiology, Pigreads can be used for reaction-diffusion systems in general. Due to its simple API, it is easy to use and integrate into existing Python code, enabling researchers to build on a solid foundation. We envision that Pigreads will be used in the future for parameter estimation, inverse problems, uncertainty quantification, sensitivity analysis, and model reduction. It could also be integrated into clinical workflows for personalised medicine.

Its nature as a Python module also facilitates building, for instance, an interactive graphical user interface or a web application on top of it. This would make it even more accessible to a wider audience, for instance, to researchers without programming experience. We are including a proof-of-concept interactive demo in the Python module allowing to attempt to terminate atrial fibrillation, i.e., spiral chaos, by tapping a touch screen.

As new models can easily be defined, another use case is designing, fitting, and testing new models. The module could also be used for educational purposes, for instance in courses on mathematical biology, dynamical systems, or scientific computing.

While Pigreads could be extended in various ways – for instance more advanced time stepping methods, adaptive mesh refinement, support for more complex geometries, or other kinds of governing equations – we have chosen to keep the module minimal to keep it as stable as possible. While we are open to receive pull requests of minor tweaks, bugfixes, and additional model definitions and geometry files, no additional features should be added unless they are absolutely necessary. There is always the possibility to fork the project and add additional features if needed, for instance, on a per-project basis.

We hope that Pigreads will be a useful tool for researchers across various fields of science and that it will enable scientific progress.

Data availability

Pigreads can be installed from PyPI. The source code is available on GitLab, and its documentation on Read the Docs.

Scripts to generate the figures and data presented in this manuscript are available in a separate Git repository.

Addenda

Acknowledgments:

DK would like to thank Martina Chirilus-Bruckner and Daniël Pijnappels for inspiring discussions.

Declaration of generative AI and AI-assisted technologies in the manuscript preparation process. During the preparation of this work the authors used ChatGPT and GitHub Copilot in order to help with writing the manuscript and minor parts of the code. After using these tools, the authors reviewed and edited the content as needed and take full responsibility for the content of the published article.

Funding:

This work was supported by the Netherlands Organisation for Scientific Research (NWO Open Mind grant 2025/TTW/02025375 to TDC). DK was supported by KU Leuven grant GPUL/20/012, and the Society, Artificial Intelligence and Life Sciences (SAILS) initiative of Leiden University.

Competing interests:

The authors have declared that no competing interests exist.

Author contributions:

DK: Conceptualization, Methodology, Software, Validation, Formal analysis, Investigation, Resources, Data Curation, Writing – Original Draft, Writing – Review & Editing, Visualization. HD: Writing – Review & Editing, Supervision. TDC: Conceptualization, Writing – Review & Editing, Supervision, Project administration, Funding acquisition.

References

Africa, P. C., Piersanti, R., Regazzoni, F., Bucelli, M., Salvador, M., Fedele, M., Pagani, S., Dede’, L., & Quarteroni, A. (2023). lifex-ep: A robust and efficient software for cardiac electrophysiology simulations. BMC Bioinformatics, 24(1). https://doi.org/10.1186/s12859-023-05513-8

Antonioletti, M., Biktashev, V. N., Jackson, A., Kharche, S. R., Stary, T., & Biktasheva, I. V. (2017). BeatBoxHPC simulation environment for biophysically and anatomically realistic cardiac electrophysiology. PLOS ONE, 12(5), e0172292. https://doi.org/10.1371/journal.pone.0172292

Barkley, D. (1991). A model for fast computer simulation of waves in excitable media. Physica D: Nonlinear Phenomena, 49(1–2), 61–70. https://doi.org/10.1016/0167-2789(91)90194-e

Berg, L. A., Oliveira, R. S., Camps, J., Wang, Z. J., Doste, R., Bueno-Orovio, A., Santos, R. W. dos, & Rodriguez, B. (2025). MonoAlg3D: Enabling cardiac electrophysiology digital twins with an efficient open source scalable solver on GPU clusters. https://doi.org/10.1101/2025.04.09.647733

Biasi, N., Seghetti, P., Parollo, M., Zucchelli, G., & Tognetti, A. (2025). A Matlab toolbox for cardiac electrophysiology simulations on patient-specific geometries. Computers in Biology and Medicine, 185, 109529. https://doi.org/10.1016/j.compbiomed.2024.109529

Bradley, C., Bowery, A., Britten, R., Budelmann, V., Camara, O., Christie, R., Cookson, A., Frangi, A. F., Gamage, T. B., Heidlauf, T., Krittian, S., Ladd, D., Little, C., Mithraratne, K., Nash, M., Nickerson, D., Nielsen, P., Nordbø, Ø., Omholt, S., Pashaei, A., Paterson, D., Rajagopal, V., Reeve, A., Röhrle, O., Safaei, S., Sebastián, R., Steghöfer, M., Wu, T., Yu, T., Zhang, H., & Hunter, P. (2011). OpenCMISS: A multi-physics & multi-scale computational infrastructure for the VPH/Physiome project. Progress in Biophysics and Molecular Biology, 107(1), 32–47. https://doi.org/10.1016/j.pbiomolbio.2011.06.015

Cannon, J., McCarthy, M. M., Lee, S., Lee, J., Börgers, C., Whittington, M. A., & Kopell, N. (2014). Neurosystems: Brain rhythms and cognitive processing. European Journal of Neuroscience, 39(5), 705–719. https://doi.org/10.1111/ejn.12453

Clayton, R., Bernus, O., Cherry, E. M., Dierckx, H., Fenton, F. H., Mirabella, L., Panfilov, A. V., Sachse, F. B., Seemann, G., & Zhang, H. (2011). Models of cardiac tissue electrophysiology: Progress, challenges and open questions. Progress in Biophysics and Molecular Biology, 104(1-3), 22–48. https://doi.org/10.1016/j.pbiomolbio.2010.05.008

Clerx, M., Collins, P., De Lange, E., & Volders, P. G. (2016). Myokit: A simple interface to cardiac cellular electrophysiology. Progress in Biophysics and Molecular Biology, 120(1-3), 100–114. https://doi.org/10.1016/j.pbiomolbio.2015.12.008

Clerx, M., Cooling, M. T., Cooper, J., Garny, A., Moyle, K., Nickerson, D. P., Nielsen, P. M., & Sorby, H. (2020). CellML 2.0. Journal of Integrative Bioinformatics, 17(2-3), 20200021. https://doi.org/10.1515/jib-2020-0021

Courant, R., Friedrichs, K., & Lewy, H. (1928). Über die partiellen Differenzengleichungen der mathematischen Physik. Mathematische Annalen, 100(1), 32–74. https://doi.org/10.1007/BF01448839

Courtemanche, M., Ramirez, R. J., & Nattel, S. (1998). Ionic mechanisms underlying human atrial action potential properties: Insights from a mathematical model. American Journal of Physiology-Heart and Circulatory Physiology, 275(1), H301–H321. https://doi.org/10.1152/ajpheart.1998.275.1.h301

De Coster, T., Claus, P., Seemann, G., Willems, R., Sipido, K. R., & Panfilov, A. V. (2018). Myocyte remodeling due to fibro-fatty infiltrations influences arrhythmogenicity. Frontiers in Physiology, 9. https://doi.org/10.3389/fphys.2018.01381

Dossel, O., Krueger, M. W., Weber, F. M., Schilling, C., Schulze, W. H. W., & Seemann, G. (2011). A framework for personalization of computational models of the human atria. 2011 Annual International Conference of the IEEE Engineering in Medicine and Biology Society, 4324–4328. https://doi.org/10.1109/iembs.2011.6091073

E. Rognes, M., E. Farrell, P., W. Funke, S., E. Hake, J., & M. C. Maleckar, M. (2017). cbcbeat: An adjoint-enabled framework for computational cardiac electrophysiology. The Journal of Open Source Software, 2(13), 224. https://doi.org/10.21105/joss.00224

Finsberg, H. (2025). fenicsx-beat - an open source simulation framework for cardiac electrophysiology. Journal of Open Source Software, 10(114), 8416. https://doi.org/10.21105/joss.08416

Finsberg, H. N. T., Herck, I. G. M. van, Daversin-Catty, C., Arevalo, H., & Wall, S. (2023). simcardems: A FEniCS-based cardiac electro-mechanics solver. Journal of Open Source Software, 8(81), 4753. https://doi.org/10.21105/joss.04753

FitzHugh, R. (1961). Impulses and physiological states in theoretical models of nerve membrane. Biophysical Journal, 1(6), 445–466. https://doi.org/10.1016/s0006-3495(61)86902-6

Gonzales, M., Guthals, A., Kerckhoffs, R., Krishnamurthy, A., Lionetti, F., Van Dorn, J., Villongco, C., Vincent, K., & McCulloch, A. (2016). Continuity: A problem-solving environment for multiscale biological modeling [Computer software]. Cardiac Mechanics Research Group; University of California, San Diego. https://continuity.ucsd.edu

Gray, P., & Scott, S. K. (1983). Autocatalytic reactions in the isothermal, continuous stirred tank reactor. Chemical Engineering Science, 38(1), 29–43. https://doi.org/10.1016/0009-2509(83)80132-8

Harris, C. R., Millman, K. J., Walt, S. J. van der, Gommers, R., Virtanen, P., Cournapeau, D., Wieser, E., Taylor, J., Berg, S., Smith, N. J., Kern, R., Picus, M., Hoyer, S., Kerkwijk, M. H. van, Brett, M., Haldane, A., Río, J. F. del, Wiebe, M., Peterson, P., Gérard-Marchant, P., Sheppard, K., Reddy, T., Weckesser, W., Abbasi, H., Gohlke, C., & Oliphant, T. E. (2020). Array programming with NumPy. Nature, 585(7825), 357–362. https://doi.org/10.1038/s41586-020-2649-2

Hodgkin, A. L., & Huxley, A. F. (1952). A quantitative description of membrane current and its application to conduction and excitation in nerve. The Journal of Physiology, 117(4), 500–544. https://doi.org/10.1113/jphysiol.1952.sp004764

Hren, R. (1996). A realistic model of the human ventricular myocardium: Application to the study of ectopic activation. [PhD thesis, Dalhousie University]. https://dalspace.library.dal.ca//handle/10222/55139

Hren, R. (1998). Value of epicardial potential maps in localizing pre-excitation sites for radiofrequency ablation. A simulation study. Physics in Medicine and Biology, 43(6), 1449–1468. https://doi.org/10.1088/0031-9155/43/6/006

Hunter, J. D. (2007). Matplotlib: A 2D graphics environment. Computing in Science & Engineering, 9(3), 90–95. https://doi.org/10.1109/MCSE.2007.55

Kaboudian, A., Cherry, E. M., & Fenton, F. H. (2019). Real-time interactive simulations of large-scale systems on personal computers and cell phones: Toward patient-specific heart modeling and other applications. Science Advances, 5(3). https://doi.org/10.1126/sciadv.aav6019

Kabus, D., Cloet, M., Zemlin, C., Bernus, O., & Dierckx, H. (2024). The Ithildin library for efficient numerical solution of anisotropic reaction-diffusion problems in excitable media. PLOS ONE, 19(9), 1–26. https://doi.org/10.1371/journal.pone.0303674

Kapral, R., & Showalter, R. (1995). Chemical waves and patterns. Kluwer.

Keldermann, R. H., ten Tusscher, K. H. W. J., Nash, M. P., Bradley, C. P., Hren, R., Taggart, P., & Panfilov, A. V. (2009). A computational study of mother rotor VF in the human ventricles. American Journal of Physiology-Heart and Circulatory Physiology, 296(2), H370–H379. https://doi.org/10.1152/ajpheart.00952.2008

Klöckner, A., Pinto, N., Lee, Y., Catanzaro, B., Ivanov, P., & Fasih, A. (2012). PyCUDA and PyOpenCL: A scripting-based approach to GPU run-time code generation. Parallel Computing, 38(3), 157–174. https://doi.org/10.1016/j.parco.2011.09.001

Lechleiter, J., Girard, S., Peralta, E., & Clapham, D. (1991). Spiral calcium wave propagation and annihilation in xenopus laevis oocytes. Science, 252(5002), 123–126. https://doi.org/10.1126/science.2011747

Leguèbe, M., Coudière, Y., Calvez, L., Pannetier, V., Douanla-Lontsi, C., Davidović, A., Bécue, P.-E., Juhoor, M., & Zemzemi, N. (2025). CEPS: A cardiac electrophysiology exploration tool for new mathematical models and methods. Journal of Open Source Software, 10(114), 8910. https://doi.org/10.21105/joss.08910

Marcotte, C. D., & Grigoriev, R. O. (2017). Dynamical mechanism of atrial fibrillation: A topological approach. Chaos: An Interdisciplinary Journal of Nonlinear Science, 27(9). https://doi.org/10.1063/1.5003259

Murray, J. (1976). On travelling wave solutions in a model for the belousov-zhabotinskii reaction. Journal of Theoretical Biology, 56(2), 329–353. https://doi.org/10.1016/S0022-5193(76)80078-1

Nagumo, J., Arimoto, S., & Yoshizawa, S. (1962). An active pulse transmission line simulating nerve axon. Proceedings of the IRE, 50(10), 2061–2070. https://doi.org/10.1109/jrproc.1962.288235

Net, T. A. A. döt, Ingy AND Müller. (2023). YAML ain’t markup language, revision 1.2.2. YAML Language Development Team. https://yaml.org/spec/1.2.2/

Nezlobinsky, T., Okenov, A., Vandersickel, N., & Panfilov, A. V. (2026). Finitewave: A lightweight and accessible framework for cardiac electrophysiology simulations [Computer software]. Ghent University, Belgium. https://github.com/finitewave/Finitewave

Niederer, S. A., Kerfoot, E., Benson, A. P., Bernabeu, M. O., Bernus, O., Bradley, C., Cherry, E. M., Clayton, R., Fenton, F. H., Garny, A., Heidenreich, E., Land, S., Maleckar, M., Pathmanathan, P., Plank, G., Rodríguez, J. F., Roy, I., Sachse, F. B., Seemann, G., Skavhaug, O., & Smith, N. P. (2011). Verification of cardiac tissue electrophysiology simulators using an N-version benchmark. Philosophical Transactions of the Royal Society A: Mathematical, Physical and Engineering Sciences, 369(1954), 4331–4351. https://doi.org/10.1098/rsta.2011.0139

Pitt-Francis, J., Pathmanathan, P., Bernabeu, M. O., Bordas, R., Cooper, J., Fletcher, A. G., Mirams, G. R., Murray, P., Osborne, J. M., Walter, A., Chapman, S. J., Garny, A., Leeuwen, I. M. M. van, Maini, P. K., Rodríguez, B., Waters, S. L., Whiteley, J. P., Byrne, H. M., & Gavaghan, D. J. (2009). Chaste: A test-driven approach to software development for biological modelling. Computer Physics Communications, 180(12), 2452–2471. https://doi.org/10.1016/j.cpc.2009.07.019

Plank, G., Loewe, A., Neic, A., Augustin, C., Huang, Y.-L., Gsell, M. A. F., Karabelas, E., Nothstein, M., Prassl, A. J., Sánchez, J., Seemann, G., & Vigmond, E. J. (2021). The openCARP simulation environment for cardiac electrophysiology. Computer Methods and Programs in Biomedicine, 208, 106223. https://doi.org/10.1016/j.cmpb.2021.106223

Rotermund, H.-H., Engel, W., Kordesch, M., & Ertl, G. (1990). Imaging of spatio-temporal pattern evolution during carbon monoxide oxidation on platinum. Nature, 343(6256), 355–357. https://doi.org/10.1038/343355a0

Stone, J. E., Gohara, D., & Shi, G. (2010). OpenCL: A parallel programming standard for heterogeneous computing systems. Computing in Science & Engineering, 12(3), 66–73. https://doi.org/10.1109/MCSE.2010.69

Sullivan, B., & Kaszynski, A. (2019). PyVista: 3D plotting and mesh analysis through a streamlined interface for the Visualization Toolkit (VTK). Journal of Open Source Software, 4(37), 1450. https://doi.org/10.21105/joss.01450

ten Tusscher, K. H. W. J., & Panfilov, A. V. (2006). Alternans and spiral breakup in a human ventricular tissue model. American Journal of Physiology-Heart and Circulatory Physiology, 291(3), H1088–H1100. https://doi.org/10.1152/ajpheart.00109.2006

Updegrove, A., Wilson, N. M., Merkow, J., Lan, H., Marsden, A. L., & Shadden, S. C. (2016). SimVascular: An open source pipeline for cardiovascular simulation. Annals of Biomedical Engineering, 45(3), 525–541. https://doi.org/10.1007/s10439-016-1762-8

van Rossum, G., Drake, F. L., et al. (1995). The Python programming language. Centrum voor Wiskunde en Informatica Amsterdam. https://www.python.org

Vázquez, M., Houzeaux, G., Koric, S., Artigues, A., Aguado-Sierra, J., Arís, R., Mira, D., Calmet, H., Cucchietti, F., Owen, H., Taha, A., Burness, E. D., Cela, J. M., & Valero, M. (2016). Alya: Multiphysics engineering simulation toward exascale. Journal of Computational Science, 14, 15–27. https://doi.org/10.1016/j.jocs.2015.12.007

Zhou, B., Balmus, M., Corrado, C., Cicci, L., Qian, S., & Niederer, S. A. (2026). TorchCor: High-performance cardiac electrophysiology simulations with the finite element method on GPUs. SoftwareX, 33, 102521. https://doi.org/10.1016/j.softx.2027.102521