Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
2449ddc
pybind: Hessian-vector product inside VMEC++ + internal Newton-Krylov…
krystophny Jul 9, 2026
b05e2da
3d external optimizer example (#618)
jurasic-pf Jul 9, 2026
4a9e717
Remove explicit test of an example script (#622)
jurasic-pf Jul 9, 2026
2e4c22d
Refactor Fourier-resolution continuation into vmecpp.run() (#619)
clazzati-pf Jul 9, 2026
271f3bf
Fix transform hot-loop regression from Eigen3 migration (#621)
jurasic-pf Jul 9, 2026
ec3ee09
Fix typos in documentation (#627)
CharlesCNorton Jul 9, 2026
4f40e83
Restore external optimizer behavioral tests (#623)
krystophny Jul 9, 2026
96f3cb3
examples: adjoint boundary sensitivities; SIMSOPT analytic gradient
krystophny Jun 14, 2026
d6d1091
examples: fix adjoint on 3D (GMRES for indefinite Hessian) + globalize
krystophny Jun 14, 2026
2f258ed
Fix adjoint checks on current main
krystophny Jul 9, 2026
d0ef637
Improve docstring formatting in simsopt_vmec_gradient.py
krystophny Jul 9, 2026
f0cb35a
Make raw VMEC forces history independent (#626)
krystophny Jul 10, 2026
385cf7e
Merge remote-tracking branch 'upstream/main' into HEAD
krystophny Jul 10, 2026
d7da6f7
Report comparable external optimizer metrics (#625)
krystophny Jul 10, 2026
c78d893
docs: fix 'As a Python package' example to reference `vmec_output` (#…
ishita-0301 Jul 10, 2026
d5dd34d
fix(docs): prevent duplicate github-pages artifacts on reruns; drop w…
Copilot Jul 10, 2026
7193329
Reduce full validation frequency (#634)
jurasic-pf Jul 11, 2026
a03183e
Fix regression in multigrid freeboundary (#640)
jurasic-pf Jul 11, 2026
a6f6485
Smaller binary wheel: Exclude recently added vmecpp_large_cpp_tests a…
jurasic-pf Jul 11, 2026
e9c70d7
Add iteration-count regression guard to multigrid free-boundary test …
jurasic-pf Jul 11, 2026
150b55e
Strict zip in continuation (#643)
jurasic-pf Jul 12, 2026
810e5c0
Decouple NESTOR vacuum solve thread count from the radial solver (#645)
jurasic-pf Jul 12, 2026
cd53871
Tolerate corrupted string variables in from_wout_file (#646)
jurasic-pf Jul 12, 2026
fa9a4e5
Fixed OMP nested behavior on older glibc versions (#647)
jurasic-pf Jul 13, 2026
ce0b6db
convert CLI option to convert input files to json (#648)
jurasic-pf Jul 15, 2026
c0ad908
Improved error messages for failed runs (#649)
jurasic-pf Jul 15, 2026
2931975
Fix CLI: keep original signature, convert as its own subcommand (#650)
jurasic-pf Jul 15, 2026
b4dcdfa
Take the multigrid rollback backup after the radial interpolation (#654)
jurasic-pf Jul 16, 2026
afcc06e
Raise allowed delt (#655)
jurasic-pf Jul 16, 2026
8482f42
Name the lambda preconditioner constants (#657)
jurasic-pf Jul 17, 2026
7eb88cb
4-point Lagrange multigrid transfer (cubic / cubic_rho) (#658)
jurasic-pf Jul 17, 2026
95fe972
Remove debug print precond (#662)
jurasic-pf Jul 17, 2026
50cabbb
Seed the vacuum state across free-boundary multigrid transitions (#663)
jurasic-pf Jul 17, 2026
8219b4e
use wout instead of output.wout in example (#664)
jurasic-pf Jul 17, 2026
08ac81f
Speed up output-stage post-processing: hoist redundant per-surface tr…
jurasic-pf Jul 19, 2026
e9985fa
return_outputs_even_if_not_converged now works for more kinds of erro…
jurasic-pf Jul 19, 2026
340806b
Run microbenchmarks longer to reduce variance (#668)
jurasic-pf Jul 19, 2026
335ef66
Hotfix CI job to unblock release (#669)
jurasic-pf Jul 19, 2026
204ab37
Allow axis coefficients in input file to be smaller than ntor (pad si…
jurasic-pf Jul 20, 2026
c3f8826
Near axis test (#670)
jurasic-pf Jul 20, 2026
f5dbf76
Add QUASR free-boundary integration tests (#656)
jurasic-pf Jul 21, 2026
3000df8
free_boundary: non-stellarator-symmetric (lasym) free-boundary suppor…
CharlesCNorton Jul 27, 2026
ccdeec5
lasym wout compare (#676)
jurasic-pf Jul 27, 2026
deec76f
Make variable names consistent and to not overlap with Python input()…
jurasic-pf Aug 5, 2026
d83035b
Add `gfortran` to list of required packages on Ubuntu - for `indata2j…
jurasic-pf Aug 5, 2026
33656d5
Merge remote-tracking branch 'origin/main' into HEAD
krystophny Aug 13, 2026
1e64e6f
Merge remote-tracking branch 'upstream/main' into HEAD
krystophny Aug 13, 2026
2cf9e68
Use SciPy root and benchmark adjoint gradient
krystophny Aug 17, 2026
da3e825
Use adaptive VMEC preconditioning with SciPy root
krystophny Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/run-benchmarks/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ runs:
for target in "${TARGETS[@]}"; do
name="${target##*:}"
bazel run --config=perf -- "${target}" \
--benchmark_min_time=0.2s \
--benchmark_min_time=1.0s \
--benchmark_format=json \
--benchmark_out="${TMP_DIR}/${name}.json"
done
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ jobs:
uses: actions/upload-pages-artifact@v3
with:
path: 'html_docs'
name: 'github-pages-${{ github.run_attempt }}'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
with:
artifact_name: 'github-pages-${{ github.run_attempt }}'
57 changes: 49 additions & 8 deletions .github/workflows/full_validation.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
name: Full V&V against reference VMEC
name: V&V against reference VMEC

# Runs the full Verification & Validation suite from proximafusion/vmecpp-validation
# (all ~219 input configurations against reference Fortran VMEC2000), using the
# vmecpp version built from this commit rather than the version pinned on PyPI/GitHub.
# This is a lot more exhaustive (and slower) than the "short" validation that
# vmecpp-validation itself runs on its own PRs, so it is not run on every push here.
# Runs the Verification & Validation suite from proximafusion/vmecpp-validation
# against reference Fortran VMEC2000, using the vmecpp version built from this
# commit rather than the version pinned on PyPI/GitHub.
#
# - On every push to main: a short run (a handful of configs, a few minutes)
# so regressions are caught immediately.
# - On release and on manual request: the full run (all ~219 configurations,
# ~8h) -- too expensive to run on every push.
on:
workflow_dispatch:
release:
types: [published]
push:
branches:
- main
Expand All @@ -16,11 +21,12 @@ concurrency:
cancel-in-progress: ${{ github.ref_name != 'main' }}

jobs:
full-validation:
name: Run full VMEC++ validation
validation:
name: Run VMEC++ validation
runs-on: ubuntu-22.04
# The full parameter scan runs ~219 configurations, each computing a reference
# wout with Fortran VMEC2000 in Docker plus one with VMEC++: budget generously.
# The short run finishes in minutes and won't come close to this limit.
timeout-minutes: 360
steps:
- name: Check out VMEC++
Expand Down Expand Up @@ -57,7 +63,13 @@ jobs:
- name: Install VMEC++ from this commit
run: python -m pip install ./vmecpp

- name: Run short validation
if: github.event_name == 'push'
working-directory: vmecpp-validation
run: python validate_vmec.py --short

- name: Run full validation
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
working-directory: vmecpp-validation
run: python validate_vmec.py

Expand All @@ -68,3 +80,32 @@ jobs:
name: vnvresults
path: vmecpp-validation/vnvresults_*/
retention-days: 30

free-boundary-quasr:
name: Run QUASR free-boundary tests
runs-on: ubuntu-22.04
timeout-minutes: 240
steps:
- uses: actions/checkout@v4
with:
lfs: true
- uses: actions/setup-python@v5
with:
python-version: "3.10"
cache: 'pip'
- name: Install required packages for Ubuntu
run: |
sudo apt-get update && sudo apt-get install -y \
build-essential cmake libnetcdf-dev liblapack-dev liblapacke-dev libopenmpi-dev \
libomp-dev libeigen3-dev nlohmann-json3-dev libhdf5-dev
- name: Install package
run: python -m pip install -v .[test]
- name: Run slow QUASR free-boundary tests
run: python -m pytest -m slow tests/test_free_boundary_quasr.py -v -ra
- name: Upload QUASR free-boundary artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: quasr-free-boundary-out
path: quasr_free_boundary_out/
retention-days: 30
2 changes: 1 addition & 1 deletion .github/workflows/pypi_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
"ubuntu:22.04",
"archlinux/archlinux",
"debian:12",
"debian:testing-20250224",
# "debian:testing-20250224", # TODO(jurasic): re-enable when 3.14 support unblocked
"fedora:41",
]
runs-on: ubuntu-24.04
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ bazel-*
src/vmecpp/cpp/_vmecpp.so

wout_solovev.nc

# generated by tests/test_free_boundary_quasr.py / examples/free_boundary_quasr_cross_sections.py
tests/data/quasr/coils.quasr*
quasr_free_boundary_out/
3 changes: 3 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ repos:
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
exclude: '^tests/data/quasr/.*\.json$'
- id: mixed-line-ending
exclude: '^tests/data/quasr/.*\.json$'
- id: name-tests-test
args: ["--pytest-test-first"]
- id: requirements-txt-fixer
- id: trailing-whitespace
exclude: '^tests/data/quasr/.*\.json$'
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v20.1.0
hooks:
Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ print(vmec_output.mercier.iota)
vmec_output.wout.save("wout_w7x.nc")
```

All other output files are accessible via members of the `output` object called `threed1_volumetrics`, `jxbout` and `mercier`.
All other output files are accessible via members of the `vmec_output` object called `threed1_volumetrics`, `jxbout` and `mercier`.

### With SIMSOPT

Expand Down Expand Up @@ -152,7 +152,7 @@ Ubuntu 22.04 and 24.04, as well as Debian 12 are officially supported.

1. Install required system packages:
```shell
sudo apt-get install -y build-essential cmake libnetcdf-dev liblapack-dev libomp-dev libhdf5-dev python3-dev
sudo apt-get install -y build-essential cmake gfortran libnetcdf-dev liblapack-dev libomp-dev libhdf5-dev python3-dev
```

2. Install VMEC++ as a Python package (possibly after creating a dedicated virtual environment):
Expand Down Expand Up @@ -279,24 +279,24 @@ VMEC++ on a configuration that is very similar to the converged equilibrium.
```python
import vmecpp

input = vmecpp.VmecInput.from_file("w7x.json")
vmec_input = vmecpp.VmecInput.from_file("w7x.json")

# Base run
output = vmecpp.run(input)
vmec_output = vmecpp.run(vmec_input)

# Now let's perturb the plasma boundary a little bit...
input.rbc[0, 0] *= 0.8
input.rbc[1, 0] *= 1.2
vmec_input.rbc[0, 0] *= 0.8
vmec_input.rbc[1, 0] *= 1.2
# ...and fix up the multigrid steps: hot-restarted runs only allow a single step
input.ns_array = input.ns_array[-1:]
input.ftol_array = input.ftol_array[-1:]
input.niter_array = input.niter_array[-1:]
vmec_input.ns_array = vmec_input.ns_array[-1:]
vmec_input.ftol_array = vmec_input.ftol_array[-1:]
vmec_input.niter_array = vmec_input.niter_array[-1:]

# We can now run with hot restart:
# passing the previously obtained output ensures that
# passing the previously obtained vmec_output ensures that
# the run starts already close to the equilibrium, so it will take
# very few iterations to converge this time!
hot_restarted_output = vmecpp.run(input, restart_from=output)
hot_restarted_output = vmecpp.run(vmec_input, restart_from=vmec_output)
```

## Full tests and validation against the reference Fortran VMEC v8.52
Expand Down Expand Up @@ -328,7 +328,7 @@ VMEC++:
- supports inputs in the classic INDATA format as well as simpler-to-parse JSON files; it is also simple to construct input objects programmatically in Python
- employs the same parallelization strategy as Fortran VMEC, but VMEC++ leverages OpenMP for a multi-thread implementation rather than Fortran VMEC's MPI parallelization: as a consequence it cannot parallelize over multiple nodes
- Uses FFT kernels optimized for small mode numbers [generated using FFTX](https://github.com/spiral-software/fftx) instead of DFT for supported resolutions. They give a 10-20% speedup relative to the DFT counterparts.
- implements the iteration algorithm of Fortran VMEC 8.52, which sometimes has different convergence behavior from (PAR)VMEC 9.0: some configurations might converge with VMEC++ and not with (PAR)VMEC 9.0, and vice versa
- implements the iteration algorithm of Fortran VMEC 8.52, which sometimes has different convergence behavior from (PAR)VMEC 9.0: some configurations might converge with VMEC++ and not with (PAR)VMEC 9.0, and vice versa. One deliberate exception: at multigrid grid transitions, the rollback backup of the state vector is taken *after* the radial interpolation of the coarse-grid solution (matching PARVMEC/VMEC2000 since 2017-01-24, "SPH 012417"), not before it as in VMEC 8.52 -- with the 8.52 ordering, the first restart of a stage silently discards the interpolated state and the finer stages effectively re-solve from a cold start

### Limitations with respect to the Fortran implementations
- non-stellarator-symmetric terms (`lasym == true`) are not supported yet
Expand Down
38 changes: 38 additions & 0 deletions benchmarks/test_benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@

import subprocess
import sys
from collections.abc import Callable
from importlib import import_module
from pathlib import Path
from typing import Any, cast

import pytest

Expand All @@ -19,6 +22,7 @@
REPO_ROOT = Path(__file__).parent.parent
TEST_DATA_DIR = REPO_ROOT / "src" / "vmecpp" / "cpp" / "vmecpp" / "test_data"
EXAMPLES_DATA_DIR = REPO_ROOT / "examples" / "data"
sys.path.insert(0, str(EXAMPLES_DATA_DIR.parent))


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -165,3 +169,37 @@ def test_bench_free_boundary(benchmark, free_boundary_input, response_table):
warmup_rounds=1,
)
assert result.wout.volume == pytest.approx(0.3075, rel=1e-3)


# ---------------------------------------------------------------------------
# SIMSOPT adjoint gradient benchmarks
# ---------------------------------------------------------------------------


def _gradient_cost(*, analytic):
module = import_module("simsopt_vmec_gradient")
gradient_cost = cast(Callable[..., Any], module.gradient_cost)

return gradient_cost(analytic=analytic)


def test_bench_simsopt_adjoint_gradient(benchmark):
"""Track the analytic adjoint gradient cost without timing assertions."""
result = benchmark.pedantic(
_gradient_cost,
kwargs={"analytic": True},
rounds=1,
warmup_rounds=0,
)
assert result.force_evals > 0


def test_bench_simsopt_finite_difference_gradient(benchmark):
"""Track the finite-difference reference cost for comparison."""
result = benchmark.pedantic(
_gradient_cost,
kwargs={"analytic": False},
rounds=1,
warmup_rounds=0,
)
assert result.force_evals > 0
15 changes: 15 additions & 0 deletions docs/api/vmecpp.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
vmecpp package
==============

.. automodule:: vmecpp
:members:
:show-inheritance:
:undoc-members:

Submodules
----------

.. toctree::
:maxdepth: 4

vmecpp.simsopt_compat
7 changes: 7 additions & 0 deletions docs/api/vmecpp.simsopt_compat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
vmecpp.simsopt\_compat module
=============================

.. automodule:: vmecpp.simsopt_compat
:members:
:show-inheritance:
:undoc-members:
4 changes: 2 additions & 2 deletions examples/data/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Example input and output files for VMEC++

A few cases are available for testing VMCE++ and experimenting with it:
A few cases are available for testing VMEC++ and experimenting with it:

1. `cth_like_fixed_bdy.json` - Stellarator case, similar to the Compact Toroidal Hybrid ([CTH](https://www.auburn.edu/cosam/departments/physics/research/plasma_physics/compact_toroidal_hybrid/index.htm)) device
1. `input.cth_like_fixed_bdy` - Fortran namelist input file to be used with Fortran VMEC
1. `cth_like_fixed_bdy.json` - JSON input file for VMEC++, derived from `input.cth_like_fixed_bdy` using [`indata2json`](https://github.com/jonathanschilling/indata2json)
1. `wout_cth_like_fixed_bdy.nc` - NetCDF output file, produced using [`PARVMEC`](https://github.com/ORNL-Fusion/PARVMEC) from `input.cth_like_fixed_bdy`, for testing the loading of a `wout` file using VMEC++'s tooling

1. `input.nfp4_QH_warm_start` - quasi-helically example for use with SIMSOPT
1. `input.nfp4_QH_warm_start` - quasi-helical example for use with SIMSOPT

1. `solovev` - axisymmetric Tokamak case, similar to the Solov'ev equilibrium used in the [1983 Hirshman & Whitson article](https://doi.org/10.1063/1.864116)
1. `input.solovev` - Fortran namelist input file for use with Fortran VMEC
Expand Down
Loading
Loading