ESSOS
Stellarator coil and particle optimization in JAX. Everything ESSOS computes — coil geometry, Biot-Savart fields, guiding-centre orbits, field lines — is differentiable end to end and runs on CPU or GPU, so a design objective and its gradient come from the same code.
pip install essos
What it does
- Differentiable throughout.
jax.gradworks through coil geometry, the field, and the traced orbits, so objectives compose without finite differences. - Coil optimization. Fit coils to a plasma boundary under length, curvature, separation and coil-surface-distance constraints.
- Particle tracing. Guiding-centre and full-orbit (Boris) models, with collisions, electric fields and alpha-loss diagnostics.
- Field-line tracing. Adaptive, arclength and toroidal-angle models, with Poincare sections.
- Fields. Biot-Savart from coils, VMEC equilibria, near-axis expansions, and
CombinedFieldto trace a sum of fields as one. - VMEC MGRID. Export coil fields and load MGRID files as JAX-compatible three-dimensional magnetic fields.
- Surfaces. Fourier-represented toroidal surfaces, from a VMEC
woutor built directly. - Parallel. JAX sharding across the visible devices; pass
devices=to pick.
Coil optimization
Fit coils to a VMEC boundary, trading normal-field error against coil length and
curvature. Losses compose with +, and L.grad is the exact gradient.
from essos.coils import Coils, CreateEquallySpacedCurves
from essos.fields import BiotSavart
from essos.losses import custom_loss
from essos.surfaces import BdotN_over_B, SurfaceRZFourier
from scipy.optimize import least_squares
surface = SurfaceRZFourier.from_wout_file("wout.nc", s=1, ntheta=30, nphi=30,
range_torus="half period")
coils = Coils(curves=CreateEquallySpacedCurves(3, 3, 10.0, 5.6, n_segments=45,
nfp=2, stellsym=True),
currents=[1.0] * 3)
L = (custom_loss(lambda field, surface: abs(BdotN_over_B(surface, field)).sum(),
"field", surface=surface)
+ custom_loss(lambda field: (field.coils.length - 32.0).clip(0).mean(), "field")
+ custom_loss(lambda field: (field.coils.curvature - 0.1).clip(0).mean(), "field"))
L.dependencies = {"field": BiotSavart(coils)}
result = least_squares(L, L.starting_dofs, L.grad, max_nfev=400)
optimized = L.dofs_to_pytree(result.x)["field"].coils
More in examples/coil_optimization.
Field-line tracing
import jax.numpy as jnp
from essos.coils import Coils
from essos.dynamics import Tracing
from essos.fields import BiotSavart
field = BiotSavart(Coils.from_json("coils.json"))
R0 = jnp.linspace(1.21, 1.40, 8)
seeds = jnp.array([R0, jnp.zeros_like(R0), jnp.zeros_like(R0)]).T
tracing = Tracing(field=field, model="FieldLineAdaptative", initial_conditions=seeds,
maxtime=8000, times_to_trace=40000, atol=1e-8, rtol=1e-8)
tracing.poincare_plot(shifts=[0.0])
More in examples/fieldline_tracing.
VMEC MGRID fields
Run examples/simple_examples/mgrid_from_coils.py
to export the included Landreman-Paul QA coils, load the file as a magnetic
field, and compare its interpolated field with direct Biot-Savart. It prints
the differences and saves a plot; the maximum difference at its four sample
points is about 0.2%. Accuracy depends on grid resolution and distance from
the coils, so compare in your region of interest before using a grid.
The cylindrical grid covers one field period in phi. The field repeats across periods, and R and Z queries are clamped to the grid bounds, so set the bounds to cover the region you intend to evaluate.
Particle tracing
from essos.constants import ALPHA_PARTICLE_CHARGE, ALPHA_PARTICLE_MASS, ONE_EV
from essos.dynamics import Particles, Tracing
particles = Particles(initial_xyz=seeds, mass=ALPHA_PARTICLE_MASS,
charge=ALPHA_PARTICLE_CHARGE, energy=4000 * ONE_EV)
tracing = Tracing(field=field, model="GuidingCenterAdaptative", particles=particles,
maxtime=1e-4, times_to_trace=800, atol=1e-7, rtol=1e-7)
tracing.plot()
print(tracing.loss_fractions)
More in examples/particle_tracing, including
full-orbit, collisional and electric-field variants.
Tracing notes
- VMEC magnetic axis. VMEC guiding centres are integrated in
sqrt(s) (cos theta, sin theta), which is regular on the axis, so orbits cross it; trajectories are returned in(s, theta, phi, ...)withthetain[0, 2 pi). A trace stops ats >= 1and reports it throughtracing.boundary_hits. Supplyingconditionreplaces that event; it is evaluated on(s, theta, phi, ...). - Stopping coil-field traces. Pass
stopping_criteria=LevelsetStoppingCriterion(...)to end Cartesian traces once they leave a prescribed distance from a surface, and read the per-line mask fromtracing.boundary_hits. - Step budget.
max_steps(default1_000_000) bounds every Diffrax solve, so a trace that cannot finish returns instead of running unbounded. - Progress bars are off by default; pass
progress=Truewhen interactive. - Model choice.
FieldLineArclengthtraces a fixed physical length so rescalingBdoes not change the run;FieldLineToroidalsets coverage directly in toroidal angle for flux-coordinate fields.
Optional: near-axis fields
Near-axis expansions need pyQSC_JAX, which is not on PyPI and therefore cannot be a declared dependency:
pip install git+https://github.com/uwplasma/pyQSC_JAX.git
Everything else works without it; the near-axis entry points raise with this command if it is missing.
Testing
pytest
License
MIT, see LICENSE.
Acknowledgments
Developed by the UWPlasma group at the University of Wisconsin-Madison, with support from Simons Foundation grant 560651.
Metadata
Release files for essos 0.18
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| essos-0.18.tar.gz | 7.3 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| essos-0.18-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 7.4 MB
Release files / essos-0.18.tar.gz
| Download URL | essos-0.18.tar.gz |
|---|---|
| Size | 7.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c2c0f76f4a253387b4d797223f1f356886939b61b9986bf61b77caabad07d967
|
|
BLAKE2b-256 checksum How to use checksums |
0d54508addad89d290b6c4ced00b756576dcbdd87fd6e79f7c923c43228768c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency logRelease files / essos-0.18-py3-none-any.whl
| Download URL | essos-0.18-py3-none-any.whl |
|---|---|
| Size | 94.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
abaf4723156ed810fe21cb6c0e4b2c89d9a50a2ae3663d39811cb57b6df34bac
|
|
BLAKE2b-256 checksum How to use checksums |
c939891ab3e6471f265128782a507361139b694479253e8acc0a9fdebc0d3989
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency log