Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
50ec5ab
examples: adjoint boundary sensitivities; SIMSOPT analytic gradient
krystophny Jun 14, 2026
8a319b6
examples: fix adjoint on 3D (GMRES for indefinite Hessian) + globalize
krystophny Jun 14, 2026
11cfee6
ideal_mhd_model: share the local force-density composition
krystophny Jun 14, 2026
f72c281
enzyme: plugin JVP entry point for the exact force Hessian
krystophny Jun 14, 2026
58bc8d0
ideal_mhd_model: exact force Hessian-vector product wrapper
krystophny Jun 14, 2026
79ef740
Merge branch 'internal-hvp' into exact-hvp-integration
krystophny Jun 14, 2026
63fbf6a
pybind: exact Hessian-vector product end-to-end (MHD + lambda)
krystophny Jun 14, 2026
b60e37d
ideal_mhd_model: add the constraint force to the exact HVP composition
krystophny Jun 14, 2026
2288a1e
ideal_mhd_model: composed==production diagnostic; gate zeroZ in exact…
krystophny Jun 14, 2026
c8f3685
exact HVP: correct geometry tangent and constraint reference -> exact…
krystophny Jun 14, 2026
48cd127
examples: add exact-autodiff-HVP Newton-Krylov solver
krystophny Jun 14, 2026
a8a4790
exact HVP: analytic geometry tangent, no finite difference, no full u…
krystophny Jun 14, 2026
54bbcfd
Merge branch 'simsopt-adjoint' into exact-hvp-integration
krystophny Jun 14, 2026
f312a71
exact HVP: cache the primal geometry across Krylov matvecs
krystophny Jun 14, 2026
a385abf
exact HVP: differentiate the ncurr=1 chi' term -> exact on cth_like
krystophny Jun 14, 2026
3ab1fc6
examples: reproducible benchmarks for the exact HVP and adjoint gradient
krystophny Jun 14, 2026
e43935b
adjoint: use the exact autodiff HVP in the boundary gradient (SIMSOPT)
krystophny Jun 14, 2026
b4669a0
optimizers: Eisenstat-Walker forcing + lgmres -> exact-HVP Newton bea…
krystophny Jun 14, 2026
267fdd0
fix CI: formatting (ruff/docformatter/clang-format) + SIMSOPT gradien…
krystophny Jun 14, 2026
c22a0c2
apply pre-commit formatting (ruff, docformatter, clang-format)
krystophny Jun 14, 2026
5ad6d37
clang-tidy: drop dead store in composition; use reinterpret_cast in J…
krystophny Jun 14, 2026
8c7a59b
bazel: declare shared force-chain kernel + autodiff headers in ideal_…
krystophny Jun 14, 2026
6b6bf44
ci: skip benchmark result upload on fork PRs (token is read-only)
krystophny Jun 14, 2026
54ccecb
ci: build VMEC2000 from source so the compat test runs on numpy 2
krystophny Jun 14, 2026
3699230
test: skip vmecpp-only indata fields in the VMEC2000 compat subset
krystophny Jun 14, 2026
22ea792
ci: skip benchmark result upload on fork PRs (token is read-only)
krystophny Jun 14, 2026
e10d2ec
ci: build VMEC2000 from source so the compat test runs on numpy 2
krystophny Jun 14, 2026
441f1d1
test: skip vmecpp-only indata fields in the VMEC2000 compat subset
krystophny Jun 14, 2026
031e739
ci: sync VMEC2000-from-source build, benchmark fork guard, abseil com…
krystophny Jun 15, 2026
cd99955
ci: sync VMEC2000-from-source build, benchmark fork guard, abseil com…
krystophny Jun 15, 2026
5fe8784
Merge remote-tracking branch 'upstream/main' into HEAD
krystophny Jun 15, 2026
3a69824
Merge remote-tracking branch 'upstream/main' into HEAD
krystophny Jun 15, 2026
161a263
enzyme: reverse-mode force VJP and transposed exact Hessian-vector pr…
krystophny Jun 15, 2026
9aae482
examples: exact boundary gradient via forward sensitivities and O(1) …
krystophny Jun 15, 2026
fb63ac5
tests: transposed exact Hessian-vector product matches the assembled …
krystophny Jun 15, 2026
27f6ea5
Merge remote-tracking branch 'origin/exact-hvp-integration' into exac…
krystophny Jun 15, 2026
43242c1
examples: O(1) structural-null detection for the reverse adjoint
krystophny Jun 15, 2026
20f8233
Apply ruff-format and docformatter (pre-commit)
krystophny Jun 16, 2026
9d89e87
bazel: declare exact_force_vjp.h header for ideal_mhd_model
krystophny Jun 16, 2026
2cf4939
ideal_mhd_model: hoist ForcesToFourier scratch out of the inner loop
krystophny Jun 16, 2026
5f08133
ideal_mhd_model: mark Jacobian metric kernel buffers __restrict
krystophny Jun 16, 2026
136d849
output_quantities: compare jcuru/jcurv at a looser opt-in tolerance
krystophny Jun 16, 2026
0638435
Merge branch 'internal-hvp' into simsopt-adjoint
krystophny Jun 24, 2026
72a1549
Merge branch 'internal-hvp' into exact-hvp-integration
krystophny Jun 24, 2026
962b999
exact-hvp-integration: align with the kernel stack
krystophny Jun 24, 2026
af212a0
enzyme: drop timing-dependent benchmark from local force Hessian test
krystophny Jun 27, 2026
9d2f380
examples: cold-start external optimizers from axis-less inputs
krystophny Jun 27, 2026
1b36dfc
examples: cold-start external optimizers from axis-less inputs
krystophny Jun 27, 2026
ee03fc0
ideal_mhd_model: make the exact HVP a consistent Jacobian by freezing…
krystophny Jun 27, 2026
985dfa3
examples: add pseudo-transient continuation solver for stiff 3D cases
krystophny Jun 27, 2026
dcd1f23
examples: docformatter-compliant docstrings (pre-commit)
krystophny Jun 27, 2026
77cf2d6
ideal_mhd_model: clang-tidy fixes for the freeze flag
krystophny Jun 27, 2026
3932fec
examples: docformatter (pinned hook) wrapping for solve_newton_ptc do…
krystophny Jun 27, 2026
2590d6f
examples: docformatter-compliant docstrings (pinned pre-commit hook)
krystophny Jun 27, 2026
a40d22b
output_quantities: compare jcuru/jcurv at a looser opt-in tolerance
krystophny Jun 16, 2026
ebe5310
Merge branch 'internal-hvp' into simsopt-adjoint
krystophny Jul 8, 2026
b832ccb
Merge branch 'augmented-force-hessian' into exact-hvp-integration
krystophny Jul 8, 2026
571225f
Merge branch 'simsopt-adjoint' into exact-hvp-integration
krystophny Jul 8, 2026
f40b2c6
ci: retry transient macos download
krystophny Jul 8, 2026
24f6de5
Merge branch 'simsopt-adjoint' into exact-hvp-integration
krystophny Jul 8, 2026
2532821
Merge internal-hvp into simsopt-adjoint
krystophny Jul 8, 2026
5afdba4
Merge simsopt-adjoint into exact-hvp-integration
krystophny Jul 8, 2026
839a8e6
Merge branch 'internal-hvp' into simsopt-adjoint
krystophny Jul 8, 2026
b230cda
Merge branch 'simsopt-adjoint' into exact-hvp-integration
krystophny Jul 8, 2026
cd3dfc2
Clean up benchmark on demandplots (#615)
jurasic-pf Jul 8, 2026
7c81177
Report less, more targeted results in FFT bench (#617)
jurasic-pf Jul 9, 2026
686bd26
Honor iteration_style=parvmec in the native solver (#612)
CharlesCNorton Jul 9, 2026
e0f1985
Abseil status handling for mgrid errors (#613)
jurasic-pf Jul 9, 2026
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
a9f7cf0
Make raw VMEC forces history independent
krystophny Jul 9, 2026
8c8a46e
Use compact storage for constraint policy
krystophny Jul 9, 2026
2def2d5
Report comparable external optimizer metrics
krystophny Jul 9, 2026
5c8acb4
Merge remote-tracking branch 'upstream/main' into exact-hvp-integration
krystophny Jul 9, 2026
371503d
Merge remote-tracking branch 'origin/simsopt-adjoint' into exact-hvp-…
krystophny Jul 9, 2026
6f92291
Merge remote-tracking branch 'origin/fix-vmec-model-pure-force' into …
krystophny Jul 9, 2026
524d797
Merge remote-tracking branch 'origin/fix-external-optimizer-benchmark…
krystophny Jul 9, 2026
0fd57f0
Count native force evaluations directly
krystophny Jul 9, 2026
6355da1
Satisfy const wrapper contract
krystophny Jul 9, 2026
4b2a9ad
Keep exact force derivatives policy-consistent
krystophny Jul 9, 2026
e2cef38
Merge remote-tracking branch 'origin/fix-external-optimizer-benchmark…
krystophny Jul 9, 2026
1c4e4fe
Run exact force integration tests with Enzyme
krystophny Jul 9, 2026
709497e
Make exact optimizer checks gate-clean
krystophny Jul 9, 2026
c347f28
Install matching OpenMP runtime for Enzyme CI
krystophny Jul 9, 2026
ac0eae6
Install VMEC build dependencies in Enzyme CI
krystophny Jul 9, 2026
d0ef637
Improve docstring formatting in simsopt_vmec_gradient.py
krystophny Jul 9, 2026
3238887
Name the m=1 gauge policy explicitly
krystophny Jul 10, 2026
c7af973
Keep optimizer metric checks with their example tests
krystophny Jul 10, 2026
e104300
Merge fix-vmec-model-pure-force into fix-external-optimizer-benchmark
krystophny Jul 10, 2026
641a3b0
Merge fix-external-optimizer-benchmark into exact-hvp-integration
krystophny Jul 10, 2026
7fa72e4
Merge simsopt-adjoint into exact-hvp-integration
krystophny Jul 10, 2026
d382efb
Run the exact-HVP benchmark in Enzyme CI
krystophny Jul 10, 2026
f0cb35a
Make raw VMEC forces history independent (#626)
krystophny Jul 10, 2026
a50975b
Merge remote-tracking branch 'upstream/main' into HEAD
krystophny Jul 10, 2026
09d1292
Remove accidentally committed VMEC++ output files from examples/data
krystophny Jul 10, 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
26 changes: 21 additions & 5 deletions .github/actions/run-benchmarks/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ runs:
export OMP_WAIT_POLICY=passive
pytest benchmarks/test_benchmarks.py --benchmark-json=benchmark_results.json

# github-action-benchmark's "pytest" tool always plots pytest-benchmark's
# `ops` field as iter/sec, but the C++ suite emits seconds.
# We convert both to the same format, so plotting is the same.
- name: Convert Python benchmark result to seconds
shell: bash
run: |
python3 benchmarks/to_seconds_json.py pytest benchmark_results.json benchmark_results_seconds.json

- name: Store / compare Python benchmark result
# Skip the result upload/compare for fork PRs: their GITHUB_TOKEN is
# read-only, so comment-on-alert/auto-push hit 'Resource not accessible
Expand All @@ -61,8 +69,8 @@ runs:
if: ${{ inputs.auto-push == 'true' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: benchmark-action/github-action-benchmark@v1.21.0
with:
tool: "pytest"
output-file-path: benchmark_results.json
tool: "customSmallerIsBetter"
output-file-path: benchmark_results_seconds.json
gh-pages-branch: benchmark-runs
benchmark-data-dir-path: benchmarks
alert-threshold: "150%"
Expand All @@ -76,7 +84,7 @@ runs:
# Builds the four benchmark cc_binary targets, runs each with
# --benchmark_format=json, and merges their output into a single
# cpp_benchmark_results.json (schema: {"context": ..., "benchmarks": [...]})
# for the store step below. Entries with "error_occurred" (e.g. an
# for the conversion step below. Entries with "error_occurred" (e.g. an
# FFT-path benchmark skipped because no FFTX codelet exists for that
# resolution) are dropped: they carry no valid timing to track.

Expand Down Expand Up @@ -142,6 +150,14 @@ runs:
f"({skipped} skipped/errored).")
PY

# github-action-benchmark's "pytest" tool always plots pytest-benchmark's
# `ops` field as iter/sec, but the C++ suite emits seconds.
# We convert both to the same format, so plotting is the same.
- name: Convert C++ benchmark result to seconds
shell: bash
run: |
python3 benchmarks/to_seconds_json.py googlecpp cpp_benchmark_results.json cpp_benchmark_results_seconds.json

- name: Store / compare C++ benchmark result
# Same fork-PR skip rationale as the Python store step above.
#
Expand All @@ -160,8 +176,8 @@ runs:
uses: benchmark-action/github-action-benchmark@v1.21.0
with:
name: "C++ Microbenchmarks"
tool: "googlecpp"
output-file-path: cpp_benchmark_results.json
tool: "customSmallerIsBetter"
output-file-path: cpp_benchmark_results_seconds.json
gh-pages-branch: benchmark-runs
benchmark-data-dir-path: benchmarks
skip-fetch-gh-pages: true
Expand Down
70 changes: 70 additions & 0 deletions .github/workflows/full_validation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Full 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.
on:
workflow_dispatch:
push:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: ${{ github.ref_name != 'main' }}

jobs:
full-validation:
name: Run full 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.
timeout-minutes: 360
steps:
- name: Check out VMEC++
uses: actions/checkout@v4
with:
path: vmecpp
lfs: true

- name: Check out vmecpp-validation
uses: actions/checkout@v4
with:
repository: proximafusion/vmecpp-validation
path: vmecpp-validation
lfs: true

- uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install required system 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 vmecpp-validation's Python requirements
run: |
cd vmecpp-validation
# Install everything except vmecpp itself: we build and install vmecpp
# from this commit below instead of the version pinned in requirements.txt.
grep -v '^vmecpp@' requirements.txt > requirements.no-vmecpp.txt
python -m pip install -r requirements.no-vmecpp.txt

- name: Install VMEC++ from this commit
run: python -m pip install ./vmecpp

- name: Run full validation
working-directory: vmecpp-validation
run: python validate_vmec.py

- name: Upload V&V results
if: always()
uses: actions/upload-artifact@v4
with:
name: vnvresults
path: vmecpp-validation/vnvresults_*/
retention-days: 30
29 changes: 28 additions & 1 deletion .github/workflows/test_enzyme.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ jobs:
sudo ./llvm.sh "${LLVM_VERSION}"
sudo apt-get install -y \
"llvm-${LLVM_VERSION}-dev" "clang-${LLVM_VERSION}" "libclang-${LLVM_VERSION}-dev" \
cmake ninja-build
"libomp-${LLVM_VERSION}-dev" \
cmake gfortran libhdf5-dev liblapack-dev libnetcdf-dev ninja-build python3-dev

- name: Cache the ClangEnzyme plugin build
id: enzyme-cache
Expand Down Expand Up @@ -75,3 +76,29 @@ jobs:
--copt="-fplugin=${ENZYME_PLUGIN}" \
--test_output=all \
//vmecpp/common/enzyme:enzyme_smoke_test

- uses: astral-sh/setup-uv@v6

- name: Run Enzyme force-derivative integration tests
env:
CC: clang-${{ env.LLVM_VERSION }}
CXX: clang++-${{ env.LLVM_VERSION }}
CMAKE_ARGS: >-
-DVMECPP_ENABLE_ENZYME=ON
-DVMECPP_ENZYME_PLUGIN=${{ env.ENZYME_PLUGIN }}
run: |
uv sync --reinstall-package vmecpp
uv run --with pytest pytest -q \
tests/test_hessian.py \
tests/test_hessian_transpose.py \
tests/test_external_optimizers.py

- name: Run exact-HVP benchmark
run: |
uv run python benchmarks/benchmark_exact_hvp.py | tee exact_hvp_benchmark.txt
{
echo "### Exact-HVP benchmark"
echo '```text'
cat exact_hvp_benchmark.txt
echo '```'
} >> "${GITHUB_STEP_SUMMARY}"
75 changes: 57 additions & 18 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -185,26 +185,65 @@ install(TARGETS indata2json DESTINATION vmecpp/cpp/third_party/indata2json/)
# -DVMECPP_ENABLE_ENZYME=ON -DVMECPP_ENZYME_PLUGIN=/path/to/ClangEnzyme-NN.so
option(VMECPP_ENABLE_ENZYME "Build Enzyme autodiff targets" OFF)
if(VMECPP_ENABLE_ENZYME)
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang|IntelLLVM")
message(FATAL_ERROR
"VMECPP_ENABLE_ENZYME requires a Clang compiler (got "
"${CMAKE_CXX_COMPILER_ID}); Enzyme attaches as a Clang plugin.")
"VMECPP_ENABLE_ENZYME requires a Clang-based compiler (got "
"${CMAKE_CXX_COMPILER_ID}); Enzyme attaches as a Clang plugin. "
"Intel oneAPI icx/icpx (IntelLLVM) is Clang-based and supported.")
endif()
set(VMECPP_ENZYME_PLUGIN "" CACHE FILEPATH "Path to ClangEnzyme-NN.so")
if(NOT VMECPP_ENZYME_PLUGIN OR NOT EXISTS "${VMECPP_ENZYME_PLUGIN}")
message(FATAL_ERROR
"VMECPP_ENABLE_ENZYME=ON requires "
"-DVMECPP_ENZYME_PLUGIN=/path/to/ClangEnzyme-NN.so")
# The Enzyme pass is an LLVM-version-specific plugin. icx/icpx (IntelLLVM)
# cannot load a plugin built against a different upstream LLVM. Enzyme's output
# is an ordinary object file, though, so for such compilers the single Enzyme
# TU (exact_force_jvp.cc) can be precompiled with a matching ClangEnzyme and
# linked in via VMECPP_ENZYME_OBJECT, while the rest is built with icx.
set(VMECPP_ENZYME_OBJECT "" CACHE FILEPATH
"Prebuilt exact_force_jvp object (compiled elsewhere with a matching ClangEnzyme)")
if(VMECPP_ENZYME_OBJECT)
if(NOT EXISTS "${VMECPP_ENZYME_OBJECT}")
message(FATAL_ERROR "VMECPP_ENZYME_OBJECT not found: ${VMECPP_ENZYME_OBJECT}")
endif()
message(STATUS "Enzyme: linking prebuilt object ${VMECPP_ENZYME_OBJECT}")
set_source_files_properties("${VMECPP_ENZYME_OBJECT}" PROPERTIES
EXTERNAL_OBJECT TRUE GENERATED TRUE)
target_sources(vmecpp_core PRIVATE "${VMECPP_ENZYME_OBJECT}")
target_compile_definitions(vmecpp_core PUBLIC VMECPP_ENABLE_ENZYME)
else()
if(NOT VMECPP_ENZYME_PLUGIN OR NOT EXISTS "${VMECPP_ENZYME_PLUGIN}")
message(FATAL_ERROR
"VMECPP_ENABLE_ENZYME=ON requires "
"-DVMECPP_ENZYME_PLUGIN=/path/to/ClangEnzyme-NN.so "
"(or -DVMECPP_ENZYME_OBJECT=/path/to/exact_force_jvp.o for icx)")
endif()
message(STATUS "Enzyme plugin: ${VMECPP_ENZYME_PLUGIN}")
enable_testing()
# Exact autodiff (forward and reverse) of a real VMEC nonlinear kernel: the
# half-grid Jacobian. Enzyme runs as an optimization-time pass, so it needs
# -O2 and the plugin attached.
add_executable(jacobian_kernel_autodiff_test
${PROJECT_SOURCE_DIR}/src/vmecpp/cpp/vmecpp/common/enzyme/jacobian_kernel_autodiff_test.cc)
target_compile_options(jacobian_kernel_autodiff_test PRIVATE
-O2 -fplugin=${VMECPP_ENZYME_PLUGIN})
add_test(NAME jacobian_kernel_autodiff COMMAND jacobian_kernel_autodiff_test)
# Exact Hessian of the composed local force map (all six force-chain kernels):
# forward/reverse Jacobian vs finite differences, and JVP cost vs FD-HVP.
add_executable(local_force_hessian_test
${PROJECT_SOURCE_DIR}/src/vmecpp/cpp/vmecpp/common/enzyme/local_force_hessian_test.cc
${PROJECT_SOURCE_DIR}/src/vmecpp/cpp/vmecpp/vmec/ideal_mhd_model/exact_force_jvp.cc)
target_compile_options(local_force_hessian_test PRIVATE
-O2 -fplugin=${VMECPP_ENZYME_PLUGIN})
add_test(NAME local_force_hessian COMMAND local_force_hessian_test)

# Wire the exact force Hessian-vector product into the core library and the
# Python extension. The Enzyme translation units are compiled with the plugin;
# the rest of the core stays normally compiled.
target_sources(vmecpp_core PRIVATE
${PROJECT_SOURCE_DIR}/src/vmecpp/cpp/vmecpp/vmec/ideal_mhd_model/exact_force_jvp.cc
${PROJECT_SOURCE_DIR}/src/vmecpp/cpp/vmecpp/vmec/ideal_mhd_model/exact_force_vjp.cc)
set_source_files_properties(
${PROJECT_SOURCE_DIR}/src/vmecpp/cpp/vmecpp/vmec/ideal_mhd_model/exact_force_jvp.cc
${PROJECT_SOURCE_DIR}/src/vmecpp/cpp/vmecpp/vmec/ideal_mhd_model/exact_force_vjp.cc
PROPERTIES COMPILE_OPTIONS "-O2;-fplugin=${VMECPP_ENZYME_PLUGIN}")
target_compile_definitions(vmecpp_core PUBLIC VMECPP_ENABLE_ENZYME)
endif()
message(STATUS "Enzyme plugin: ${VMECPP_ENZYME_PLUGIN}")
enable_testing()
# Self-contained over flat buffers; checks Jv and J^T u against finite
# differences and against each other (adjoint identity). Enzyme runs as an
# optimization-time pass, so it needs -O2 and the plugin attached. -O2 also
# guards against a Debug (-O0) configuration where the AD pass would not run.
add_executable(jacobian_kernel_autodiff_test
${PROJECT_SOURCE_DIR}/src/vmecpp/cpp/vmecpp/common/enzyme/jacobian_kernel_autodiff_test.cc)
target_compile_options(jacobian_kernel_autodiff_test PRIVATE
-O2 -fplugin=${VMECPP_ENZYME_PLUGIN})
add_test(NAME jacobian_kernel_autodiff COMMAND jacobian_kernel_autodiff_test)
endif()
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

[![CI](https://github.com/proximafusion/vmecpp/actions/workflows/tests.yaml/badge.svg)](https://github.com/proximafusion/vmecpp/actions/workflows/tests.yaml)
[![C++ core tests](https://github.com/proximafusion/vmecpp/actions/workflows/test_bazel.yaml/badge.svg)](https://github.com/proximafusion/vmecpp/actions/workflows/test_bazel.yaml)
[![Full V&V against reference VMEC](https://github.com/proximafusion/vmecpp/actions/workflows/full_validation.yaml/badge.svg)](https://github.com/proximafusion/vmecpp/actions/workflows/full_validation.yaml)
[![Publish wheels to PyPI](https://github.com/proximafusion/vmecpp/actions/workflows/pypi_publish.yml/badge.svg)](https://github.com/proximafusion/vmecpp/actions/workflows/pypi_publish.yml)

VMEC++ is a Python-friendly, from-scratch reimplementation in C++ of the Variational Moments Equilibrium Code (VMEC),
Expand Down Expand Up @@ -315,6 +316,10 @@ The single-thread runtimes as well as the contents of the "wout" file produced b
The full validation test can be found at https://github.com/proximafusion/vmecpp-validation, including a set of sensible input configurations,
parameter scan values and tolerances that make the comparison pass. See that repo for more information.

This full validation (~219 input configurations) is run against every commit to `main` and can also be triggered
on demand from the [Full V&V against reference VMEC](https://github.com/proximafusion/vmecpp/actions/workflows/full_validation.yaml) workflow
(click "Run workflow"). It builds `vmecpp` from the corresponding commit rather than using the version pinned by `vmecpp-validation`.

## Differences with respect to PARVMEC/VMEC2000

VMEC++:
Expand Down
53 changes: 53 additions & 0 deletions benchmarks/benchmark_exact_hvp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# SPDX-FileCopyrightText: 2024-present Proxima Fusion GmbH
# <info@proximafusion.com>
#
# SPDX-License-Identifier: MIT
"""Benchmark exact autodiff Hessian-vector products as an internal solver.

Compare preconditioned JFNK, finite-difference Newton-HVP, and exact-autodiff
Newton-HVP on the same VMEC++ residual. Report force evaluations, iterations,
wall time, residual norm, and energy error. Requires an Enzyme-enabled build.

Run: python benchmarks/benchmark_exact_hvp.py
"""

import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "examples"))
import external_optimizers as eo # type: ignore # noqa: E402

DATA = ROOT / "examples" / "data"
CASES = [
("solovev", DATA / "solovev.json", 11),
("cth_like", DATA / "cth_like_fixed_bdy.json", 11),
]

SOLVERS = [
("precond JFNK", eo.solve_newton_krylov_preconditioned),
("Newton FD-HVP + M^-1", eo.solve_newton_hvp),
("Newton exact-HVP + M^-1", eo.solve_newton_exact_hvp),
]


def main():
for case, path, ns in CASES:
print(f"\n=== {case} (ns={ns}) ===")
_, wref = eo.reference_equilibrium(path, ns)
print(f"native solve: W = {wref:.8e}")
print(
f"{'optimizer':28s} {'F-evals':>8s} {'iters':>6s} {'time[s]':>8s} "
f"{'||F||':>10s} {'dW':>10s}"
)
for label, solver in SOLVERS:
result = solver(path, ns)[1]
print(
f"{label:28s} {result.force_evals:8d} {result.outer_iters:6d} "
f"{result.seconds:8.2f} {result.residual_norm:10.1e} "
f"{abs(result.energy - wref):10.1e}"
)


if __name__ == "__main__":
main()
Loading