A Python implementation of SpinW for spin wave calculations using linear spin wave theory (LSWT).
- Crystal Structure: Define lattices with space group symmetry (P1, Pm-3m, Fd-3m, P-3, etc.)
- Magnetic Atoms: Arbitrary spin quantum number S and g-tensor
- Exchange Interactions: Heisenberg, anisotropic, Dzyaloshinskii-Moriya (DM)
- Magnetic Structures: Ferromagnetic, antiferromagnetic, helical, spiral, 120° order
- Spin Wave Theory: Full BdG Hamiltonian diagonalization via σz·H method
- Neutron Scattering: S(Q,ω) with magnetic form factors
- Multiple Backends: NumPy (CPU), PyTorch (GPU-accelerated)
pip install pyspinwavegit clone https://github.com/scattering/pyspinw.git
cd pyspinw
pip install -e .pip install pyspinwave[gpu]pip install -e ".[dev]"The project includes a Docker-based build script for reproducible builds:
# Clean build artifacts
./scripts/build.sh clean
# Build package in Docker
./scripts/build.sh build
# Build and publish to PyPI
./scripts/build.sh publish
# Run tests in Docker
./scripts/build.sh testRequires Docker (or OrbStack on macOS) to be running.
import numpy as np
from pyspinw import SpinW
# Create spin model
sw = SpinW()
sw.genlattice(lat_const=[3, 8, 8], angled=[90, 90, 90])
sw.addatom(r=[0, 0, 0], S=1, label='Fe')
sw.gencoupling(max_distance=4)
# Add ferromagnetic exchange J = -1 meV
sw.addmatrix(label='J', value=-1.0)
sw.addcoupling(mat='J', bond=1)
# Set ferromagnetic ground state
sw.genmagstr(mode='direct', k=[0, 0, 0], S=np.array([[0, 0, 1]]))
# Calculate spin wave dispersion
spec = sw.spinwave([[0, 0, 0], [1, 0, 0]], n_pts=200)
# Plot
import matplotlib.pyplot as plt
q = np.linspace(0, 1, 200)
plt.plot(q, spec['omega'][:, 0])
plt.xlabel('q (r.l.u.)')
plt.ylabel('Energy (meV)')
plt.title('FM Chain Dispersion')
plt.show()from pyspinw import SpinW, sw_model
# Use predefined model
sw = sw_model('triAF', J=1.0) # J = 1 meV AFM
# Or build manually:
sw = SpinW()
sw.genlattice(lat_const=[4, 4, 6], angled=[90, 90, 120])
sw.addatom(r=[0, 0, 0], S=1.5, label='Mn')
sw.gencoupling(max_distance=5)
sw.addmatrix(label='J1', value=1.0)
sw.addcoupling(mat='J1', bond=1)
# 120° helical structure
sw.genmagstr(mode='helical', k=[1/3, 1/3, 0], n=[0, 0, 1])
# Calculate along Γ-K-M path
spec = sw.spinwave([[0, 0, 0], [1/3, 1/3, 0], [0.5, 0, 0]], n_pts=150)from pyspinw import SpinW
import numpy as np
sw = SpinW()
sw.genlattice(lat_const=[4, 4, 10], angled=[90, 90, 90])
sw.addatom(r=[0, 0, 0], S=1, label='Cu')
sw.gencoupling(max_distance=5)
# Heisenberg exchange
sw.addmatrix(label='J', value=1.0)
sw.addcoupling(mat='J', bond=1)
# DM interaction D·(Si × Sj)
sw.addmatrix(label='D', value=[0, 0, 0.1]) # D_z = 0.1 meV
sw.addcoupling(mat='D', bond=1)
# Néel order
sw.genmagstr(mode='direct', k=[0.5, 0.5, 0], S=np.array([[0, 0, 1]]))
spec = sw.spinwave([[0, 0, 0], [0.5, 0.5, 0]], n_pts=100)The main class for spin wave calculations.
sw.genlattice(lat_const=[a, b, c], angled=[α, β, γ], spgr='P 1')lat_const: Lattice parameters [a, b, c] in Ångströmsangled: Lattice angles [α, β, γ] in degreesspgr: Space group symbol (e.g., 'Fm-3m', 'P 63/mmc')
sw.addatom(r=[x, y, z], S=spin, label='atom_name')r: Fractional coordinates [x, y, z]S: Spin quantum number (e.g., 0.5, 1, 1.5)label: Atom label for identification
sw.gencoupling(max_distance=10.0)Generates all symmetry-equivalent bonds up to max_distance Å.
# Heisenberg (isotropic)
sw.addmatrix(label='J1', value=1.0)
# Anisotropic exchange
sw.addmatrix(label='J_aniso', value=[[Jxx, Jxy, Jxz],
[Jxy, Jyy, Jyz],
[Jxz, Jyz, Jzz]])
# Dzyaloshinskii-Moriya
sw.addmatrix(label='DM', value=[Dx, Dy, Dz])sw.addcoupling(mat='J1', bond=1) # Assign J1 to nearest neighbors
sw.addcoupling(mat='J2', bond=2) # Assign J2 to next-nearest neighbors# Ferromagnetic
sw.genmagstr(mode='direct', k=[0, 0, 0], S=np.array([[0, 0, 1]]))
# Antiferromagnetic (Néel)
sw.genmagstr(mode='direct', k=[0.5, 0.5, 0], S=np.array([[0, 0, 1]]))
# Helical/Spiral
sw.genmagstr(mode='helical', k=[1/3, 1/3, 0], n=[0, 0, 1])mode: 'direct' for explicit spins, 'helical' for spiral structuresk: Propagation vector in reciprocal lattice unitsn: Rotation axis for helical structuresS: Spin vectors, shape (n_atoms, 3)
spec = sw.spinwave(Q_path, n_pts=100, formfact=False)Q_path: List of Q-points defining the path, e.g.,[[0,0,0], [1,0,0]]n_pts: Number of points along the pathformfact: Include magnetic form factor (default: False)
Returns dictionary with:
omega: Spin wave energies, shape (n_pts, n_modes)Sab: Dynamical structure factor componentshkl: Q-points along path
E = sw.energy() # Returns energy per site in meVfrom pyspinw.utils import sw_egrid
spec = sw_egrid(spec, dE=0.1, Emin=0, Emax=10, n_E=100)Convolves with Gaussian of width dE meV.
from pyspinw.utils import sw_plotspec
sw_plotspec(spec, mode='color') # Color plot of S(Q,ω)
sw_plotspec(spec, mode='line') # Line plot of dispersionfrom pyspinw import sw_model
sw = sw_model('chain', J=1.0) # 1D antiferromagnetic chain
sw = sw_model('squareAF', J=1.0) # 2D square lattice AFM
sw = sw_model('triAF', J=1.0) # 2D triangular lattice 120° AFMPySpinW implements linear spin wave theory (LSWT) following the Holstein-Primakoff transformation:
S⁺ ≈ √(2S) a
S⁻ ≈ √(2S) a†
Sᶻ = S - a†a
The spin Hamiltonian is expanded to quadratic order in bosonic operators, yielding a BdG Hamiltonian:
H = Σ_q [A(q) B(q) ] [a_q ]
[B*(q) A*(q)] [a†_{-q}]
Diagonalization via the σz·H method gives spin wave energies ω(q).
PySpinW has been validated against:
- Analytical results: FM chain ω(q) = 2|J|S(1-cos(2πq)) with < 10⁻⁹ error
- Sunny.jl: 6/6 SpinW tutorial ports pass (SW01-SW08, CoRh2O4)
- spinw_torch: Minimalist PyTorch reference implementation
pyspinw/
├── pyspinw/ # Package directory (import as: from pyspinw import ...)
│ ├── __init__.py # Package exports
│ ├── core/
│ │ ├── spinw.py # Main SpinW class
│ │ ├── lswt.py # Linear spin wave theory
│ │ ├── lattice.py # Crystal lattice
│ │ ├── magnetic.py # Magnetic structures
│ │ ├── coupling.py # Exchange couplings
│ │ └── constants.py # Physical constants
│ ├── backends/
│ │ ├── numpy_backend.py # CPU implementation
│ │ └── pytorch_backend.py # GPU implementation
│ └── utils/
│ ├── egrid.py # Energy convolution
│ ├── plotting.py # Visualization
│ └── neutron.py # Neutron scattering
├── tests/ # Test suite
│ ├── test_spinw.py
│ └── test_sunny_comparison.py
├── examples/ # Example scripts and notebooks
│ ├── quickstart.ipynb
│ └── demos.py
├── scripts/
│ └── build.sh # Docker build script
├── pyproject.toml # Build configuration
├── README.md
├── CHANGELOG.md
└── CONTRIBUTING.md
# All tests
pytest
# With coverage
pytest --cov=pyspinw
# Specific test file
pytest tests/test_sunny_comparison.py -v- Space groups: 9 of 230 implemented (P1, Pm-3m, Fm-3m, Fd-3m, P-3, P63/mmc, I4/mmm, R-3m, Pnma)
- Form factors: Simplified dipole approximation
- Single-ion anisotropy: Basic support only
- Powder averaging: Not yet implemented
- Inelastic neutron scattering: S(Q,ω) without resolution convolution
- SpinW - Original MATLAB implementation
- Sunny.jl - Julia spin dynamics package
- Toth & Lake, J. Phys.: Condens. Matter 27, 166002 (2015)
- Colpa, Physica 93A, 327 (1978)
GPL-3.0 (following the original SpinW license)
Contributions welcome! Please see CONTRIBUTING.md and submit issues/PRs on GitHub.
If you use PySpinW in your research, please cite:
@software{PySpinW,
title = {PySpinW: Python Spin Wave Calculator},
url = {https://github.com/scattering/pyspinw},
version = {0.1.0},
year = {2025}
}And the original SpinW paper:
@article{spinw,
author = {Toth, S. and Lake, B.},
title = {Linear spin wave theory for single-Q incommensurate magnetic structures},
journal = {J. Phys.: Condens. Matter},
volume = {27},
pages = {166002},
year = {2015},
doi = {10.1088/0953-8984/27/16/166002}
}