GPU-accelerated cardiac electrophysiology simulation in PyTorch
TorchCor is a high-performance simulator for cardiac electrophysiology (CEP) using the finite element method (FEM) on general-purpose GPUs. Built on top of PyTorch, TorchCor delivers substantial computational acceleration for large-scale CEP simulations, with seamless integration into modern deep learning workflows and efficient handling of complex mesh geometries.
TorchCor offers:
- 🚀 Fast, scalable CEP simulations on large and complex heart meshes
- 🔗 Seamless integration with PyTorch and scientific machine learning workflows
- ⚙️ Support for a wide range of ionic models and conductivity heterogeneity
- 📈 Generation of precise local activation and repolarization time maps with monodomain or reaction-eikonal
- 🩺 Simulation of clinically relevant 12-lead ECG signals via the lead-field method
Below are simulation results showcasing the electrical activation patterns over time in the left atrium and bi-ventricle.
![]() 3D left atrium surface mesh |
![]() 3D bi-ventricle volume mesh |
pip install torchcorNote: Requires PyTorch with CUDA support for GPU acceleration.
Here’s a concise example to run a simulation using the TenTusscher-Panfilov ionic model on a bi-ventricle mesh.
🧩 Mesh file:
You can download the bi-ventricle mesh from the following link:
Google Drive – Bi-ventricle Mesh
The inputs are:
.ptsfile: coordinates of the vertices.elemfile: connectivities of the mesh.lonfile: fibre orientations- One or more
.vtxfiles: pacing locations
Running the following code will produce:
- a list of membrane potentials, each saved after every 1 ms of the simulation
- a local activation time map and a repolarisation time map
all saved in .pt file format readable by torch.load. This execution takes 47.6 seconds on RTX 5090.
import torchcor as tc
from torchcor.simulator import Monodomain
from torchcor.ionic import TenTusscherPanfilov
from pathlib import Path
# Specify the GPU device for running the simulation
tc.set_device("cuda:0")
dtype = tc.float32
# The total simulation duration (ms)
simulation_time = 500
dt = 0.01
home_dir = Path.home()
mesh_dir = home_dir / "Data/ventricle/Case_1"
# Load in the ionic model. Here we use TenTusscherPanfilov for the simulation on bi-ventricle
im = TenTusscherPanfilov(cell_type="ENDO", dt=dt, dtype=dtype)
# 1. Initialise the Monodomain model
simulator = Monodomain(ionic_models=[im], T=simulation_time, dt=dt, dtype=dtype)
# 2. Load in the mesh files (.pts .elem .lon)
simulator.load_mesh(path=mesh_dir)
# 3. Specify the conductivity for each region
simulator.add_conductivity([34, 35], il=0.5272, it=0.2076, el=1.0732, et=0.4227)
simulator.add_conductivity([44, 45, 46], il=0.9074, it=0.3332, el=0.9074, et=0.3332)
# 4. Specify the locations where stimulation is applied
simulator.add_stimulus(mesh_dir / "LV_sf.vtx", start=0.0, duration=1.0, intensity=100)
simulator.add_stimulus(mesh_dir / "LV_pf.vtx", start=0.0, duration=1.0, intensity=100)
simulator.add_stimulus(mesh_dir / "LV_af.vtx", start=0.0, duration=1.0, intensity=100)
simulator.add_stimulus(mesh_dir / "RV_sf.vtx", start=5.0, duration=1.0, intensity=100)
simulator.add_stimulus(mesh_dir / "RV_mod.vtx", start=5.0, duration=1.0, intensity=100)
# 5. Start the simulation
snapshot_interval = 1
Vm = simulator.solve(a_tol=1e-5, # absolute tolerance
r_tol=1e-5, # relative tolerance
max_iter=100, # maximum number of iterations for each CG calculation
snapshot_interval=snapshot_interval, # save the solution after every 1 ms
verbose=True,
result_path="./biventricle") # the folder in which the results are saved
# POSTPROCESSING:
ATs = simulator.compute_activation_map(Vm=Vm,
snapshot_interval=snapshot_interval,
threshold=0)
print("ATs: ", ATs.min().item(), ATs.cpu().max().item(), flush=True)
RTs = simulator.compute_repolarization_map(Vm=Vm,
snapshot_interval=snapshot_interval,
threshold=-70)
print("RTs: ", RTs.min().item(), RTs.cpu().max().item(), flush=True)
simulator.vm_to_vtk(Vm=Vm, step=10)TorchCor reconstructs the clinical 12-lead ECG directly from a cardiac simulation using the lead-field (reciprocity) method on a coupled heart–torso mesh. Each electrode's lead field is precomputed with a single elliptic solve; the entire ECG time series then follows from one matrix–vector product with the transmembrane potential Vm, so evaluating the ECG over a whole beat is effectively free.

Simulated 12-lead ECG from a full heart–torso model, reconstructed with the lead-field reciprocity method.
import torch
from torchcor.ecg import LeadField
# Coupled heart–torso mesh; precompute one lead field per electrode
lf = LeadField(torso_mesh_dir, heart_mesh_dir,
device=torch.device("cuda:0"), dtype=torch.float64)
# Passive torso/organ conductivities (S/m) + anisotropic bidomain heart conductivities
lf.add_torso_conductivity([10, 11, 12], g=0.6667)
lf.add_heart_conductivity([24, 25], il=0.5272, it=0.2076, el=1.0732, et=0.4227)
lf.build()
# Electrodes (V1–V6, RA, LA, RL, LL); one elliptic solve per lead
lf.load_electrodes("electrodes/lf_src.vtx")
lf.precompute_all()
# Vm: (T, N_heart) from the EP simulation -> dict of the 12 standard leads
ecg = lf.compute_12lead(Vm)A complete heart → torso → 12-lead pipeline is provided in torchcor/demo/ecg_12lead.py.
For a GPU-enabled Docker setup to run TorchCor without installing dependencies on your host system, please see the docker/ folder.
If you use TorchCor in academic work, please consider citing:
@article{zhou2026torchcor,
title={TorchCor: High-performance cardiac electrophysiology simulations with the finite element method on GPUs},
author={Zhou, Bei and Balmus, Maximilian and Corrado, Cesare and Cicci, Ludovica and Qian, Shuang and Niederer, Steven A},
journal={SoftwareX},
volume={33},
pages={102521},
year={2026},
publisher={Elsevier}
}TorchCor is developed and maintained by Bei Zhou, Maximilian Balmus, Cesare Corrado, Shuang Qian, and Steven A. Niederer in the Cardiac Electro-Mechanics Research Group (CEMRG) at Imperial College London.
We welcome contributions from the community! Feel free to open issues or submit pull requests.


