fenicsx-beat


Namefenicsx-beat JSON
Version 0.2.2 PyPI version JSON
download
home_pageNone
SummaryLibrary to run cardiac EP simulations
upload_time2025-07-21 14:44:31
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords cardiac electrophysiology
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ![_](https://raw.githubusercontent.com/finsberg/fenicsx-beat/refs/heads/main/docs/_static/logo.png)

# fenicsx-beat
Cardiac electrophysiology simulator in FEniCSx

- Source code: https://github.com/finsberg/fenicsx-beat
- Documentation: https://finsberg.github.io/fenicsx-beat


## Install
You can install the library with `pip`
```
python3 -m pip install fenicsx-beat
```
or with `conda`
```
conda install -c conda-forge fenicsx-beat
```
Note that installing with `pip` requires [FEniCSx already installed](https://fenicsproject.org/download/)

## Getting started

The following minimal example demonstrates simulating the Monodomain model on a unit square domain using a modified FitzHugh-Nagumo model

```python
import shutil

import matplotlib.pyplot as plt
import numpy as np

from mpi4py import MPI
import dolfinx
import ufl

import beat

# MPI communicator
comm = MPI.COMM_WORLD
# Create mesh
mesh = dolfinx.mesh.create_unit_square(comm, 32, 32, dolfinx.cpp.mesh.CellType.triangle)
# Create a variable for time
time = dolfinx.fem.Constant(mesh, dolfinx.default_scalar_type(0.0))


# Define forward euler scheme for solving the ODEs
# This just needs to be a function that takes the current time, states, parameters and dt
# and returns the new states
def fitzhughnagumo_forward_euler(t, states, parameters, dt):
    s, v = states
    (
        c_1,
        c_2,
        c_3,
        a,
        b,
        v_amp,
        v_rest,
        v_peak,
        stim_amplitude,
        stim_duration,
        stim_start,
    ) = parameters
    i_app = np.where(
        np.logical_and(t > stim_start, t < stim_start + stim_duration),
        stim_amplitude,
        0,
    )
    values = np.zeros_like(states)

    ds_dt = b * (-c_3 * s + (v - v_rest))
    values[0] = ds_dt * dt + s

    v_th = v_amp * a + v_rest
    I = -s * (c_2 / v_amp) * (v - v_rest) + (
        ((c_1 / v_amp**2) * (v - v_rest)) * (v - v_th)
    ) * (-v + v_peak)
    dV_dt = I + i_app
    values[1] = v + dV_dt * dt
    return values


# Define space for the ODEs
ode_space = dolfinx.fem.functionspace(mesh, ("P", 1))

# Define parameters for the ODEs
a = 0.13
b = 0.013
c1 = 0.26
c2 = 0.1
c3 = 1.0
v_peak = 40.0
v_rest = -85.0
stim_amplitude = 100.0
stim_duration = 1
stim_start = 0.0

# Collect the parameter in a numpy array
parameters = np.array(
    [
        c1,
        c2,
        c3,
        a,
        b,
        v_peak - v_rest,
        v_rest,
        v_peak,
        stim_amplitude,
        stim_duration,
        stim_start,
    ],
    dtype=np.float64,
)

# Define the initial states
init_states = np.array([0.0, -85], dtype=np.float64)
# Specify the index of state for the membrane potential
# which will also inform the PDE solver later
v_index = 1

# We can also check the solution of the ODE
# by solving the ODE for a single cell
times = np.arange(0.0, 1000.0, 0.1)
values = np.zeros((len(times), 2))
values[0, :] = np.array([0.0, -85.0])
for i, t in enumerate(times[1:]):
    values[i + 1, :] = fitzhughnagumo_forward_euler(t, values[i, :], parameters, dt=0.1)


fig, ax = plt.subplots()
ax.plot(times, values[:, v_index])
ax.set_xlabel("Time")
ax.set_ylabel("States")
ax.legend()
fig.savefig("ode_solution.png")


# Now we set external stimulus to zero for ODE
parameters[-3] = 0.0

# and create stimulus for PDE
stim_expr = ufl.conditional(ufl.And(ufl.ge(time, 0.0), ufl.le(time, 0.5)), 600.0, 0.0)
stim_marker = 1
cells = dolfinx.mesh.locate_entities(
    mesh, mesh.topology.dim, lambda x: np.logical_and(x[0] <= 0.5, x[1] <= 0.5)
)
stim_tags = dolfinx.mesh.meshtags(
    mesh,
    mesh.topology.dim,
    cells,
    np.full(len(cells), stim_marker, dtype=np.int32),
)
dx = ufl.Measure("dx", domain=mesh, subdomain_data=stim_tags)
I_s = beat.Stimulus(expr=stim_expr, dZ=dx, marker=stim_marker)

# Create PDE model
pde = beat.MonodomainModel(time=time, mesh=mesh, M=0.001, I_s=I_s, dx=dx)

# Next we create the PDE solver where we make sure to
# pass the variable for the membrane potential from the PDE
ode = beat.odesolver.DolfinODESolver(
    v_ode=dolfinx.fem.Function(ode_space),
    v_pde=pde.state,
    fun=fitzhughnagumo_forward_euler,
    init_states=init_states,
    parameters=parameters,
    num_states=len(init_states),
    v_index=1,
)

# Combine PDE and ODE solver
solver = beat.MonodomainSplittingSolver(pde=pde, ode=ode)

# Now we setup file for saving results
# First remove any existing files
shutil.rmtree("voltage.bp", ignore_errors=True)

vtx = dolfinx.io.VTXWriter(mesh.comm, "voltage.bp", [pde.state], engine="BP5")
vtx.write(0.0)

# Finally we run the simulation for 400 ms using a time step of 0.01 ms
T = 400.0
t = 0.0
dt = 0.01
i = 0
while t < T:
    v = solver.pde.state.x.array
    solver.step((t, t + dt))
    t += dt
    if i % 500 == 0:
        vtx.write(t)
    i += 1

vtx.close()

```
![_](https://raw.githubusercontent.com/finsberg/fenicsx-beat/refs/heads/main/docs/_static/simple.gif)
![_](https://raw.githubusercontent.com/finsberg/fenicsx-beat/refs/heads/main/joss-paper/paper_figure.png)

See more examples in the [documentation](https://finsberg.github.io/fenicsx-beat)

## License
MIT

## Need help or having issues
Please submit an [issue](https://github.com/finsberg/fenicsx-beat/issues)

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "fenicsx-beat",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "cardiac, electrophysiology",
    "author": null,
    "author_email": "Henrik Finsberg <henriknf@simula.no>",
    "download_url": "https://files.pythonhosted.org/packages/66/24/39f13b92b8389163c887e90f6c0879f6a00f8b93c6e4bc19de22ebb4a82f/fenicsx_beat-0.2.2.tar.gz",
    "platform": null,
    "description": "![_](https://raw.githubusercontent.com/finsberg/fenicsx-beat/refs/heads/main/docs/_static/logo.png)\n\n# fenicsx-beat\nCardiac electrophysiology simulator in FEniCSx\n\n- Source code: https://github.com/finsberg/fenicsx-beat\n- Documentation: https://finsberg.github.io/fenicsx-beat\n\n\n## Install\nYou can install the library with `pip`\n```\npython3 -m pip install fenicsx-beat\n```\nor with `conda`\n```\nconda install -c conda-forge fenicsx-beat\n```\nNote that installing with `pip` requires [FEniCSx already installed](https://fenicsproject.org/download/)\n\n## Getting started\n\nThe following minimal example demonstrates simulating the Monodomain model on a unit square domain using a modified FitzHugh-Nagumo model\n\n```python\nimport shutil\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nfrom mpi4py import MPI\nimport dolfinx\nimport ufl\n\nimport beat\n\n# MPI communicator\ncomm = MPI.COMM_WORLD\n# Create mesh\nmesh = dolfinx.mesh.create_unit_square(comm, 32, 32, dolfinx.cpp.mesh.CellType.triangle)\n# Create a variable for time\ntime = dolfinx.fem.Constant(mesh, dolfinx.default_scalar_type(0.0))\n\n\n# Define forward euler scheme for solving the ODEs\n# This just needs to be a function that takes the current time, states, parameters and dt\n# and returns the new states\ndef fitzhughnagumo_forward_euler(t, states, parameters, dt):\n    s, v = states\n    (\n        c_1,\n        c_2,\n        c_3,\n        a,\n        b,\n        v_amp,\n        v_rest,\n        v_peak,\n        stim_amplitude,\n        stim_duration,\n        stim_start,\n    ) = parameters\n    i_app = np.where(\n        np.logical_and(t > stim_start, t < stim_start + stim_duration),\n        stim_amplitude,\n        0,\n    )\n    values = np.zeros_like(states)\n\n    ds_dt = b * (-c_3 * s + (v - v_rest))\n    values[0] = ds_dt * dt + s\n\n    v_th = v_amp * a + v_rest\n    I = -s * (c_2 / v_amp) * (v - v_rest) + (\n        ((c_1 / v_amp**2) * (v - v_rest)) * (v - v_th)\n    ) * (-v + v_peak)\n    dV_dt = I + i_app\n    values[1] = v + dV_dt * dt\n    return values\n\n\n# Define space for the ODEs\node_space = dolfinx.fem.functionspace(mesh, (\"P\", 1))\n\n# Define parameters for the ODEs\na = 0.13\nb = 0.013\nc1 = 0.26\nc2 = 0.1\nc3 = 1.0\nv_peak = 40.0\nv_rest = -85.0\nstim_amplitude = 100.0\nstim_duration = 1\nstim_start = 0.0\n\n# Collect the parameter in a numpy array\nparameters = np.array(\n    [\n        c1,\n        c2,\n        c3,\n        a,\n        b,\n        v_peak - v_rest,\n        v_rest,\n        v_peak,\n        stim_amplitude,\n        stim_duration,\n        stim_start,\n    ],\n    dtype=np.float64,\n)\n\n# Define the initial states\ninit_states = np.array([0.0, -85], dtype=np.float64)\n# Specify the index of state for the membrane potential\n# which will also inform the PDE solver later\nv_index = 1\n\n# We can also check the solution of the ODE\n# by solving the ODE for a single cell\ntimes = np.arange(0.0, 1000.0, 0.1)\nvalues = np.zeros((len(times), 2))\nvalues[0, :] = np.array([0.0, -85.0])\nfor i, t in enumerate(times[1:]):\n    values[i + 1, :] = fitzhughnagumo_forward_euler(t, values[i, :], parameters, dt=0.1)\n\n\nfig, ax = plt.subplots()\nax.plot(times, values[:, v_index])\nax.set_xlabel(\"Time\")\nax.set_ylabel(\"States\")\nax.legend()\nfig.savefig(\"ode_solution.png\")\n\n\n# Now we set external stimulus to zero for ODE\nparameters[-3] = 0.0\n\n# and create stimulus for PDE\nstim_expr = ufl.conditional(ufl.And(ufl.ge(time, 0.0), ufl.le(time, 0.5)), 600.0, 0.0)\nstim_marker = 1\ncells = dolfinx.mesh.locate_entities(\n    mesh, mesh.topology.dim, lambda x: np.logical_and(x[0] <= 0.5, x[1] <= 0.5)\n)\nstim_tags = dolfinx.mesh.meshtags(\n    mesh,\n    mesh.topology.dim,\n    cells,\n    np.full(len(cells), stim_marker, dtype=np.int32),\n)\ndx = ufl.Measure(\"dx\", domain=mesh, subdomain_data=stim_tags)\nI_s = beat.Stimulus(expr=stim_expr, dZ=dx, marker=stim_marker)\n\n# Create PDE model\npde = beat.MonodomainModel(time=time, mesh=mesh, M=0.001, I_s=I_s, dx=dx)\n\n# Next we create the PDE solver where we make sure to\n# pass the variable for the membrane potential from the PDE\node = beat.odesolver.DolfinODESolver(\n    v_ode=dolfinx.fem.Function(ode_space),\n    v_pde=pde.state,\n    fun=fitzhughnagumo_forward_euler,\n    init_states=init_states,\n    parameters=parameters,\n    num_states=len(init_states),\n    v_index=1,\n)\n\n# Combine PDE and ODE solver\nsolver = beat.MonodomainSplittingSolver(pde=pde, ode=ode)\n\n# Now we setup file for saving results\n# First remove any existing files\nshutil.rmtree(\"voltage.bp\", ignore_errors=True)\n\nvtx = dolfinx.io.VTXWriter(mesh.comm, \"voltage.bp\", [pde.state], engine=\"BP5\")\nvtx.write(0.0)\n\n# Finally we run the simulation for 400 ms using a time step of 0.01 ms\nT = 400.0\nt = 0.0\ndt = 0.01\ni = 0\nwhile t < T:\n    v = solver.pde.state.x.array\n    solver.step((t, t + dt))\n    t += dt\n    if i % 500 == 0:\n        vtx.write(t)\n    i += 1\n\nvtx.close()\n\n```\n![_](https://raw.githubusercontent.com/finsberg/fenicsx-beat/refs/heads/main/docs/_static/simple.gif)\n![_](https://raw.githubusercontent.com/finsberg/fenicsx-beat/refs/heads/main/joss-paper/paper_figure.png)\n\nSee more examples in the [documentation](https://finsberg.github.io/fenicsx-beat)\n\n## License\nMIT\n\n## Need help or having issues\nPlease submit an [issue](https://github.com/finsberg/fenicsx-beat/issues)\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Library to run cardiac EP simulations",
    "version": "0.2.2",
    "project_urls": {
        "Documentation": "https://finsberg.github.io/fenicsx-beat",
        "Homepage": "https://finsberg.github.io/fenicsx-beat",
        "Source": "https://github.com/finsberg/fenicsx-beat",
        "Tracker": "https://github.com/finsberg/fenicsx-beat/issues"
    },
    "split_keywords": [
        "cardiac",
        " electrophysiology"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "e7b34938e4cc09caee2bb32e5edaff3962c32adde69735294dd0c21248c3bc1f",
                "md5": "f7f650847f0719a0100e468a9ef8449b",
                "sha256": "80fb116581f46a893b89aa1a03bbeeda44a2b3466bd1b4352366f1168a94e642"
            },
            "downloads": -1,
            "filename": "fenicsx_beat-0.2.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f7f650847f0719a0100e468a9ef8449b",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 27985,
            "upload_time": "2025-07-21T14:44:29",
            "upload_time_iso_8601": "2025-07-21T14:44:29.863005Z",
            "url": "https://files.pythonhosted.org/packages/e7/b3/4938e4cc09caee2bb32e5edaff3962c32adde69735294dd0c21248c3bc1f/fenicsx_beat-0.2.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "662439f13b92b8389163c887e90f6c0879f6a00f8b93c6e4bc19de22ebb4a82f",
                "md5": "86fc0b9af739063956af4cdc27ce37c4",
                "sha256": "d4a164d92980e1a1d326d887815d6e5831c4bc84064e84b55daeb15ec47ac045"
            },
            "downloads": -1,
            "filename": "fenicsx_beat-0.2.2.tar.gz",
            "has_sig": false,
            "md5_digest": "86fc0b9af739063956af4cdc27ce37c4",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 33123,
            "upload_time": "2025-07-21T14:44:31",
            "upload_time_iso_8601": "2025-07-21T14:44:31.347521Z",
            "url": "https://files.pythonhosted.org/packages/66/24/39f13b92b8389163c887e90f6c0879f6a00f8b93c6e4bc19de22ebb4a82f/fenicsx_beat-0.2.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-21 14:44:31",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "finsberg",
    "github_project": "fenicsx-beat",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "fenicsx-beat"
}
        
Elapsed time: 1.51594s