Spynach

A Python reimplementation of the Spinach spin dynamics kernel, verified stage by stage against the MATLAB original.

Portrait of Spinoza rendered as a two-dimensional 1H-13C correlation spectrum: coloured cross peaks on a black background with the 1D projections along the top and right edges
Any resemblance to living or dead personalities is purely accidental. Fig. credits to Florin Teleanu.
Goals

What the project is for

  • Python conveniences, no MATLAB licence. Spynach is a pip-installable package that slots into the scientific Python stack: combine it with NumPy, SciPy, fitting and machine-learning libraries or your own code, run it on any machine with nothing to buy or activate, or open the quick-start notebook in the browser, where it installs itself in a click.
  • A readable, maintainable kernel. The Spinach algorithms in plain Python with NumPy and SciPy, with the same names and conventions wherever that is possible.
  • Verified, not eyeballed. Every ported stage is compared with reference data computed by Spinach itself, with tolerances that are stated and explained.
  • Ready for accelerators. The hot loops go through one backend adapter so that a GPU array library can be plugged in; that step is deferred until a target device is chosen.
  • Later: a differentiable engine for optimal control and fitting, and possibly a tensor-train backend for chain-like coupling topologies.
Approach

How the port is done

  • An oracle harness. MATLAB dump scripts write the intermediate results of a Spinach calculation to HDF5 fixtures; parity tests load them and compare the Python results at each stage (oracle/README.md).
  • Stage by stage. Spin system, basis, operators, Hamiltonian, propagation, relaxation, contexts, pulse sequences: each lands with its fixtures, tests and documentation before the next starts (roadmap).
  • Two formalisms behind one basis abstraction. Dense Zeeman-Hilbert space for small systems, the spherical-tensor Liouville space with state-space restriction (IK-0, IK-1, IK-2) for large liquid-state systems.
  • Nothing silently ignored. Any Spinach option that is not yet ported raises an error rather than being dropped.
  • Executable documentation. Every guide's code blocks run in the test suite and are rendered into notebooks with their outputs.
Spinach

Relationship to the 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.

Scope

What is ported

Available now

  • Liquid-state pulse-acquire in Hilbert space (zeeman-hilb)
  • Liouville space (sphten-liouv): restricted basis, superoperators, states, propagation with zero track elimination and path tracing, chemical exchange
  • Anisotropic interactions with the crystal and powder contexts and Spinach's grids
  • Redfield relaxation, scalar relaxation of the first and second kind, T1/T2 and damping models, thermalisation
  • Magic angle spinning in Fokker-Planck form (singlerot)
  • COSY, NOESY, HSQC with isotopomer dilution, apodisation, 2D plotting

Not yet

  • Symmetry groups in the basis
  • The zeeman-liouv formalism
  • Decoupling inside the contexts
  • GPU execution (the entry points are marked)
  • ESR, DNP, imaging and optimal control modules

The function-by-function table is in the README.

Beyond the port

Add-ons originating here

  • Coherence transfer pathway selection. 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).
  • Non-ideal pulses and cogwheel search. Each pulse is a perfect 180°, an imperfect 180° or a small-flip-angle pulse, with the leakage of every coherence transfer after J. Chem. Phys. 113, 979 (2000); cogwheel cycles are found by exact integer arithmetic, all winding numbers tested at once, and crusher schemes by the DOTCOPS objective.
  • Interactive figures. Plotly figures from every plotting function, FID and time-series plots with no Spinach equivalent, and figure_png for static copies.
  • Verification tooling. 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.
  • Executable documentation. Guides whose code runs in the tests, notebooks generated with outputs, a Colab quick start, contributed notebooks.

All of it, with an example for each entry, is in the guide Beyond the port.

Quick start

Install and run

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:

Documentation

Guides and notebooks

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.

GuideCoversOpen
Hilbert spaceliquid-state NMR in Hilbert space: setup, the simulation pipeline, pulse sequences, tolerances, parity with Spinach
Liouville spacethe spherical-tensor formalism: basis descriptor, state-space restriction, filters, chemical subsystems
Solid stateanisotropic interactions: tensors, dipolar couplings from coordinates, quadrupolar couplings, crystal and powder contexts, grids
RelaxationRedfield theory for rotational diffusion, T1/T2 and damping models, thermal equilibrium, transient NOE
Magic angle spinningthe Fokker-Planck treatment of sample rotation: rotor phase grid, the singlerot context, sidebands, rotor echoes
Pulse sequenceswriting sequences, apodisation and 2D plots, decoupling and homospoil, COSY, NOESY, HSQC with isotopomer dilution
Coherence transfer pathways: worked examplesa cookbook for ctps, from a double-quantum filter to MRS crushers, and every check of the module's tests as a runnable example
Beyond the portfeatures with no Spinach counterpart, each with an example: ctps, non-ideal pulses and cogwheel search, interactive figures, compare and the parity page, executable documentation
Coming from Spinachwhat carries over from Spinach and what differs: spin systems, basis, operators, Hamiltonians, parameters, modalities, processing

The documentation index explains how the guides, notebooks and the examples fit together.

Examples

Scripts and contributed notebooks

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.

ExampleWhat it calculatesOpen
23Na triple-quantum build-upcontributed 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
noesy_strychnineSpinach's examples/nmr_liquids/noesy_strychnine.m: 22 protons, IK-2 basis, Redfield relaxation, 500 ms NOESY at 512 x 512 points
maz_noesy_2Spinach'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
li_exchange_equilibriumcontributed 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
zulf_pf6_1dcontributed Spinach script (Florin Teleanu): PF6− at 2.7 µT, adiabatic-drop state from file, selective pulse, 16 384-point zero- to ultralow-field spectrum

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.

Verification

Parity and timing

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.

noesy_strychnine

22 protons, IK-2 basis (12 316 states), Redfield relaxation, 500 ms NOESY, 512 x 512 points.

FID agreement
2.2e-12 relative
liquid/noesy stage
93.3 s Spynach, 83.0 s Spinach
whole case
93.6 s Spynach, 110.9 s Spinach
Open the report

zulf_pf6_1d

31P and six 19F at 2.7 µT, complete basis (16 384 states), lab frame, selective pulse and 16 384-point acquisition.

FID agreement
1.5e-12 relative
acquisition stage
46.9 s Spynach, 40.0 s Spinach
whole case
47.1 s Spynach, 69.0 s Spinach
Open the report

li_exchange_equilibrium

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.

trajectory agreement
4e-13 relative
states
1e-17 relative or better
size
31 states, seconds on both sides
Open the report

maz_noesy_2

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.

FID agreement
1.2e-10 relative
liquid/noesy stage
1702 s Spynach, 841 s Spinach
where the time goes
the SRFK integral: 26 squarings of a 38 628-dimensional sparse matrix
Open the report

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.

Credits

With thanks

Grateful to Claude for the amazing work, and to Ilya for encouragement and for suggesting the catchy name!