A Python reimplementation of the Spinach spin dynamics kernel, verified stage by stage against the MATLAB original.
Spynach derives from Spinach 2.13, the spin dynamics package of Ilya Kuprov and co-workers, released under the MIT licence. The translation keeps the Spinach attribution and notice in LICENSE, and keeps Spinach's function names, parameter structures and tolerances so that an existing Spinach script maps onto Spynach line by line.
The deliberate differences are small: spin indices are 0-based, product and sum operators are told apart by the type of the first argument, spin system objects are immutable, and time series are 1-D arrays. The guide Coming from Spinach lists, feature by feature, what is the same, what differs and what is not yet ported.
The translation from MATLAB to Python was carried out largely by an AI coding agent working under the direction of Alexej Jerschow, who set the goals and reviewed the results; correctness rests on the test suite against Spinach reference data.
zeeman-hilb)sphten-liouv): restricted basis, superoperators, states, propagation with zero track elimination and path tracing, chemical exchangecrystal and powder contexts and Spinach's gridssinglerot)zeeman-liouv formalismThe function-by-function table is in the README.
ctps, a separate module in the repository (import ctps, no dependency on spynach), ports the CCCP.m and CCCP++ programs: every pathway a sequence allows, weighted by phase cycle, gradients, diffusion and pulses, ranked and drawn in an interactive explorer that steps through the pathways with their factors (guide, notebook).figure_png for static copies.compare checks any result against a Spinach run; the parity page is regenerated from the test suite; the comparison protocol times and profiles both sides into one report.All of it, with an example for each entry, is in the guide Beyond the port.
Python 3.11 or newer. Install from the repository, or clone it and install in editable mode with the docs extra to open the notebooks.
pip install "spynach @ git+https://github.com/ajerschow/spynach.git"
# or, for the notebooks and tests
git clone https://github.com/ajerschow/spynach.git
cd spynach && pip install -e ".[docs,test]"
Two coupled protons at 14.1 T, pulse-acquire, exponential apodisation, two doublets:
import numpy as np
import spynach
spin_system = spynach.create(
{"isotopes": ["1H", "1H"], "magnet": 14.1},
{"zeeman": {"scalar": [1.00, 1.10]}, "coupling": {"scalar": [[0, 12.0], [0, 0]]}},
)
spin_system = spynach.basis(spin_system, {"formalism": "zeeman-hilb", "approximation": "none"})
pars = {"spins": ["1H"], "offset": 630, "sweep": 200, "npoints": 1024,
"rho0": spynach.state(spin_system, "L+", "1H"),
"coil": spynach.state(spin_system, "L+", "1H")}
fid = spynach.liquid(spin_system, spynach.acquire, pars, "nmr")
fid = spynach.apodisation(fid, [("exp", 6)])
spectrum = np.fft.fftshift(np.fft.fft(fid, 4096))
A caveat for containers: when the CPU quota is below the number of cores the container can see, OpenBLAS oversubscribes the quota and dense LAPACK calls slow down badly. Set OPENBLAS_NUM_THREADS to the quota there; on an ordinary machine nothing needs to be set.
To try the example without installing anything, open it as a notebook that installs Spynach and plots the spectrum:
Each guide is a Markdown document whose code runs in the test suite, and a Jupyter notebook generated from it with its outputs and interactive plots. The notebook links open in nbviewer, which renders the Plotly figures interactively; GitHub's own viewer shows the static copies saved with them.
| Guide | Covers | Open |
|---|---|---|
| Hilbert space | liquid-state NMR in Hilbert space: setup, the simulation pipeline, pulse sequences, tolerances, parity with Spinach | guide · notebook |
| Liouville space | the spherical-tensor formalism: basis descriptor, state-space restriction, filters, chemical subsystems | guide · notebook |
| Solid state | anisotropic interactions: tensors, dipolar couplings from coordinates, quadrupolar couplings, crystal and powder contexts, grids | guide · notebook |
| Relaxation | Redfield theory for rotational diffusion, T1/T2 and damping models, thermal equilibrium, transient NOE | guide · notebook |
| Magic angle spinning | the Fokker-Planck treatment of sample rotation: rotor phase grid, the singlerot context, sidebands, rotor echoes | guide · notebook |
| Pulse sequences | writing sequences, apodisation and 2D plots, decoupling and homospoil, COSY, NOESY, HSQC with isotopomer dilution | guide · notebook |
| Coherence transfer pathways: worked examples | a cookbook for ctps, from a double-quantum filter to MRS crushers, and every check of the module's tests as a runnable example | guide · notebook |
| Beyond the port | features with no Spinach counterpart, each with an example: ctps, non-ideal pulses and cogwheel search, interactive figures, compare and the parity page, executable documentation | guide · notebook |
| Coming from Spinach | what carries over from Spinach and what differs: spin systems, basis, operators, Hamiltonians, parameters, modalities, processing | guide · notebook |
The documentation index explains how the guides, notebooks and the examples fit together.
The guides run reduced versions of Spinach examples in seconds. The scripts in examples/ are the same calculations at the Spinach size, for side-by-side comparison with MATLAB: each writes its results next to itself and can report the normwise error against a .mat file saved by the Spinach script. Python code contributed by users lives in user_py/ as notebooks saved with their outputs.
| Example | What it calculates | Open |
|---|---|---|
| 23Na triple-quantum build-up | contributed notebook: one 23Na spin under quadrupolar Redfield relaxation towards thermal equilibrium (Cq = 200 kHz, τc = 1 ns, 14.1 T); from the pulsed thermal state, the rank-3 single-quantum tensor, the triple-quantum coherence after a second pulse and the longitudinal recovery over 10 ms, against the biexponential form of Jaccard, Wimperis and Bodenhausen | notebook · source |
| noesy_strychnine | Spinach's examples/nmr_liquids/noesy_strychnine.m: 22 protons, IK-2 basis, Redfield relaxation, 500 ms NOESY at 512 x 512 points | script |
| maz_noesy_2 | Spinach's examples/relaxation_theory/maz_noesy_2.m: methylaziridine with CSA tensors, a 14N quadrupole and scalar relaxation of the first and second kind, 2 s NOESY | script |
| li_exchange_equilibrium | contributed Spinach script (Florin Teleanu): two-site 7Li exchange with quadrupolar and shift tensors, thermal state equilibrated under exchange, thermalised Redfield relaxation, 2 s trajectory analysed with trajan and stateinfo | script · original |
| zulf_pf6_1d | contributed Spinach script (Florin Teleanu): PF6− at 2.7 µT, adiabatic-drop state from file, selective pulse, 16 384-point zero- to ultralow-field spectrum | script · original |
The two full-size NOESY scripts and the two contributed scripts are also the cases of the timed parity and timing reports. Contributions to user_py/ keep their author's name in the file and in its README.
The parity suites compare Spynach with Spinach 2.13 fixtures at every stage of a calculation. Of the 540 tests, 323 are fixture comparisons over 45 fixture cases: Liouville-space setup (132), Liouville-space propagation (54), Hilbert-space pulse-acquire (64), anisotropic interactions (25), relaxation (20), scalar relaxation (9), magic angle spinning (16), pulse sequences (3). Comparisons are relative with zero absolute tolerance; the largest measured difference is 8e-11 for quantities compared at 1e-9 (time evolution and FIDs) and 9e-15 for everything compared at 1e-10 or tighter (operators, states, Hamiltonians, propagators). Every case, tolerance and measured difference is listed in docs/parity.md (regenerated from the suite by oracle/parity_summary.py).
Two full-size calculations are run on both sides under a profiler, on the same machine, and summarised in an HTML report with the stage timings, the normwise difference of every saved array, the figures and the profiler tops. The stage timings below are wall clock for each implementation as shipped: Spinach with its parallel pool and multithreaded BLAS, Spynach as a single process with multithreaded BLAS inside dense calls.
22 protons, IK-2 basis (12 316 states), Redfield relaxation, 500 ms NOESY, 512 x 512 points.
31P and six 19F at 2.7 µT, complete basis (16 384 states), lab frame, selective pulse and 16 384-point acquisition.
Two 7Li sites with quadrupolar and shift tensors in chemical exchange: thermal state equilibrated under exchange, Redfield relaxation thermalised to it, a 2 s trajectory analysed by tensor projections and state classes.
Methylaziridine, 7 protons + 14N with DFT shielding tensors and the 14N quadrupole: Redfield theory with scalar relaxation of the first and second kind, IK-2 basis (19 314 states), 2 s NOESY.
Both runs: Apple silicon Mac, 12 cores; MATLAB R2026b with an 11-worker pool; Python 3.12 with NumPy and SciPy on Accelerate. The protocol, including how to add a case, is in comparison/README.md.
Grateful to Claude for the amazing work, and to Ilya for encouragement and for suggesting the catchy name!