Metadata-Version: 2.4
Name: magnetics
Version: 1.0.2
Summary: A NumPy-native finite-difference micromagnetics simulation engine
Author: Parasveer
License: MIT
Project-URL: Homepage, https://pypi/project/magnetics
Project-URL: GitHub, https://github.com/Parasveer/magnetics
Project-URL: Bug Tracker, https://github.com/Parasveer/magnetics/issues
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.26
Requires-Dist: scipy>=1.11
Provides-Extra: gpu
Requires-Dist: cupy-cuda12x; extra == "gpu"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Dynamic: license-file

# Magnetics

A NumPy-native finite-difference micromagnetics simulation engine.

Unlike closed, script-driven binaries (e.g. MuMax3), Magnetics keeps
magnetization state and field data as ordinary NumPy (or CuPy) arrays, so
they compose directly with the rest of the scientific Python ecosystem:
autodiff frameworks, ML surrogates, batched parameter sweeps, and Jupyter
workflows.

A static-plotting visualization layer is in progress: accuracy/convergence
surface plots and 2D vector/scalar field slices are done; scalar-vs-time
plots, an interactive PyVista-based 3D viewer, and a simulation-recorder
mechanism are not yet built.

## Why this exists, and why you should trust the numbers

MuMax3 is fast and well-established, but it's a closed, script-driven
binary — magnetization lives in a proprietary run, exported after the
fact. In Magnetics, magnetization is a live array in your Python process,
so it composes directly with autodiff frameworks for gradient-based
inverse design, batches along an array axis instead of spawning
subprocesses, and drops into Jupyter without a file-format round-trip.

Every physics layer in this engine is gated on a numeric check against a
hand-computed value, an analytic solution, or a convergence trend —
never "looks right." A few examples, not just claims:

- The exchange field is checked against a symbolically-derived analytic
  Bloch-wall energy (0.0093% error, confirmed convergent across 3
  resolutions).
- The demagnetization tensor is checked against four independent
  algebraic identities (exact cube self-demag factor, trace-zero,
  on-axis symmetry, far-field dipole limit) *before* being wired into the
  FFT convolution, which is separately cross-checked against a literal
  brute-force double sum.
- The full multi-cell LLG integrator is checked against the
  closed-form macrospin solution on a single cell (not just its own
  internal consistency), and the core relaxation gate — total energy
  strictly decreasing under undriven damped relaxation — is verified on
  every single step of a real multi-cell trajectory.
- OVF file I/O was cross-validated against a real, independent copy of
  **MuMax3 3.12**: a file written by Magnetics was loaded via
  `m.LoadFile`, and MuMax3's own re-saved output was decoded and
  numerically diffed against the original values (agreement to float32
  precision, the expected limit given MuMax3's binary output format).

A few real bugs were caught this way during development — a mistranscribed
tensor formula, a broadcasting bug, a wrong relative import, an invisible
quiver plot.

## Installation

```bash
pip install magnetics
```

For GPU (CuPy) support:

```bash
pip install "magnetics[gpu]"
```

For visualization (matplotlib):

```bash
pip install "magnetics[viz]"
```

## Quick example

```python
from magnetics import Mesh, Material, MaterialMap
from magnetics.llg import solve_llg
from magnetics.viz import plot_vector_field_2d

grid = Mesh(nx=40, ny=40, nz=1, dx=3e-9, dy=3e-9, dz=3e-9)
grid.set_ring(r_outer=55e-9, r_inner=25e-9)

mm = MaterialMap()
mm.register(0, Material(name="Permalloy", Ms=8.6e5, Aex=1.3e-11, alpha=0.5))

import numpy as np
m0 = np.zeros(grid.shape + (3,))
m0[grid.geometry, 2] = 1.0

result = solve_llg(m0, grid, mm, t_span=(0.0, 2e-11), dt=5e-14)
plot_vector_field_2d(result.m[-1], grid)
```

## Structure

```
magnetics/
  __init__.py        public API re-exports
  backend.py           NumPy <-> CuPy array-module abstraction
  constants.py          shared physical constants (MU0)
  mesh.py                Mesh: grid, geometry masking, region indexing
  materials.py             Material, MaterialMap: per-cell parameter lookup
  llg/
    macrospin.py            single-macrospin LLG solver
    multicell.py              backend-agnostic multi-cell RK4 integrator
    effective_field.py         sums the field terms for LLG assembly
  fields/
    exchange.py               exchange field + energy
    demag.py                    demagnetization field + energy (FFT/Newell tensor)
    zeeman.py                    Zeeman field + energy
    anisotropy.py                 uniaxial anisotropy field + energy
    [dmi.py                        not yet implemented]
  energy/
    total_energy.py            sums the per-term energies
  io/
    ovf.py                     OVF 2.0 read/write (validated against real MuMax3)
  viz/
    diagnostics.py              accuracy/convergence surface plots
    fields.py                     2D vector/scalar field slice plots
  api/                    [not yet implemented] scripting DSL
tests/                 verification suite, one file per module (122 tests)
examples/              runnable scripts, numbered by layer
.github/workflows/      CI (test matrix)
```

## License

MIT — see [`LICENSE`](LICENSE).
