# PySpinW

[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)

A Python implementation of [SpinW](https://spinw.org) for spin wave calculations using linear spin wave theory (LSWT).

## Features

- **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)

## Installation

### From PyPI (when published)
```bash
pip install PySpinW
```

### From source
```bash
git clone https://github.com/pyspinw/PySpinW.git
cd PySpinW
pip install -e .
```

### With GPU support
```bash
pip install -e ".[gpu]"
```

### Development installation
```bash
pip install -e ".[dev]"
```

## Quick Start

### Example 1: Ferromagnetic Chain

```python
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()
```

### Example 2: Triangular Antiferromagnet with 120° Order

```python
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)
```

### Example 3: Square Lattice AFM with DM Interaction

```python
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)
```

## API Reference

### SpinW Class

The main class for spin wave calculations.

#### Lattice Definition

```python
sw.genlattice(lat_const=[a, b, c], angled=[α, β, γ], spgr='P 1')
```
- `lat_const`: Lattice parameters [a, b, c] in Ångströms
- `angled`: Lattice angles [α, β, γ] in degrees
- `spgr`: Space group symbol (e.g., 'Fm-3m', 'P 63/mmc')

#### Adding Atoms

```python
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

#### Generating Couplings

```python
sw.gencoupling(max_distance=10.0)
```
Generates all symmetry-equivalent bonds up to `max_distance` Å.

#### Adding Exchange Matrices

```python
# 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])
```

#### Assigning Couplings

```python
sw.addcoupling(mat='J1', bond=1)  # Assign J1 to nearest neighbors
sw.addcoupling(mat='J2', bond=2)  # Assign J2 to next-nearest neighbors
```

#### Magnetic Structure

```python
# 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 structures
- `k`: Propagation vector in reciprocal lattice units
- `n`: Rotation axis for helical structures
- `S`: Spin vectors, shape (n_atoms, 3)

#### Spin Wave Calculation

```python
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 path
- `formfact`: Include magnetic form factor (default: False)

Returns dictionary with:
- `omega`: Spin wave energies, shape (n_pts, n_modes)
- `Sab`: Dynamical structure factor components
- `hkl`: Q-points along path

#### Energy Calculation

```python
E = sw.energy()  # Returns energy per site in meV
```

### Utility Functions

#### Energy Broadening

```python
from 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.

#### Plotting

```python
from pyspinw.utils import sw_plotspec

sw_plotspec(spec, mode='color')  # Color plot of S(Q,ω)
sw_plotspec(spec, mode='line')   # Line plot of dispersion
```

### Predefined Models

```python
from 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° AFM
```

## Physics Background

### Linear Spin Wave Theory

PySpinW 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).

### Validation

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

## Project Structure

```
PySpinW/
├── pyspinw/             # Package directory (import as: import pyspinw)
│   ├── __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_spinw.py # Unit tests (21 tests)
│   │   └── test_sunny_comparison.py  # Sunny.jl validation (6 tests)
│   └── examples/
│       ├── quickstart.ipynb  # Tutorial notebook
│       └── demos.py     # Example scripts
├── pyproject.toml       # Build configuration
├── README.md
├── CHANGELOG.md
└── CONTRIBUTING.md
```

## Running Tests

```bash
# All tests
pytest

# With coverage
pytest --cov=pyspinw

# Specific test file
pytest tests/test_sunny_comparison.py -v
```

## Known Limitations

- **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

## References

- [SpinW](https://spinw.org) - Original MATLAB implementation
- [Sunny.jl](https://github.com/SunnySuite/Sunny.jl) - Julia spin dynamics package
- Toth & Lake, J. Phys.: Condens. Matter 27, 166002 (2015)
- Colpa, Physica 93A, 327 (1978)

## License

GPL-3.0 (following the original SpinW license)

## Contributing

Contributions welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) and submit issues/PRs on GitHub.

## Citation

If you use PySpinW in your research, please cite:

```bibtex
@software{PySpinW,
  title = {PySpinW: Python Spin Wave Calculator},
  url = {https://github.com/pyspinw/PySpinW},
  version = {0.1.0},
  year = {2024}
}
```

And the original SpinW paper:

```bibtex
@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}
}
```
