Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
66664d3
enzyme: exact autodiff of the VMEC Jacobian kernel (forward vs reverse)
krystophny Jun 14, 2026
ebd1699
ideal_mhd_model: share the Jacobian kernel between solver and autodiff
krystophny Jun 14, 2026
43886f9
bazel: declare force-chain kernel headers in ideal_mhd_model (sandbox…
krystophny Jun 14, 2026
19e27e6
ci: re-trigger (transient apt-403 on packages.microsoft.com)
krystophny Jun 14, 2026
ce7129c
ci: skip benchmark result upload on fork PRs (token is read-only)
krystophny Jun 14, 2026
9611219
ci: build VMEC2000 from source so the compat test runs on numpy 2
krystophny Jun 14, 2026
7ff2aef
test: skip vmecpp-only indata fields in the VMEC2000 compat subset
krystophny Jun 14, 2026
40a33c9
ci: sync VMEC2000-from-source build, benchmark fork guard, abseil com…
krystophny Jun 15, 2026
f15d8af
Merge remote-tracking branch 'upstream/main' into HEAD
krystophny Jun 15, 2026
1c5c653
ideal_mhd_model: mark Jacobian kernel buffers __restrict
krystophny Jun 16, 2026
27d36d2
output_quantities: compare jcuru/jcurv at a looser opt-in tolerance
krystophny Jun 16, 2026
4d76ed6
Merge branch 'main' into exact-hessian-jacobian
jurasic-pf Jun 23, 2026
d5f0215
Merge branch 'enzyme-build-option' into exact-hessian-jacobian
krystophny Jun 24, 2026
24ece82
output_quantities: compare jcuru/jcurv at the standard tolerance
krystophny Jun 24, 2026
776d247
Migrate large cpp tests to the main repository (#596)
jurasic-pf Jun 25, 2026
b25cfdd
Make the lambda Fourier resolution independent of the geometry (#598)
CharlesCNorton Jun 26, 2026
484b8fe
Update bazel lock (#604)
jurasic-pf Jul 6, 2026
d0e474d
Benchmark CI action remove code duplication (#605)
jurasic-pf Jul 7, 2026
9345694
Continue to higher multigrid resolutions from hot restart (#603)
jurasic-pf Jul 7, 2026
aff1a53
Merge branch 'main' into exact-hessian-jacobian
jurasic-pf Jul 7, 2026
6d4a597
Python-side resolution continuation (#544)
CharlesCNorton Jul 7, 2026
f5b7ac7
ideal_mhd_model: share the Jacobian kernel with exact autodiff (fwd v…
krystophny Jul 7, 2026
07ba4fd
ideal_mhd_model: share the metric kernel (gsqrt, guu, guv, gvv) (#568)
krystophny Jul 8, 2026
10938d7
ideal_mhd_model: share the contravariant-field kernel (bsupu, bsupv) …
krystophny Jul 8, 2026
d1c2328
Add C++ Google Benchmark microbenchmarks for critical hot functions (…
jurasic-pf Jul 8, 2026
c55210c
ideal_mhd_model: share the covariant-field kernel (bsubu, bsubv) (#570)
krystophny Jul 8, 2026
d203333
ideal_mhd_model: share the magnetic-pressure kernel (#571)
krystophny Jul 8, 2026
acd99d5
ideal_mhd_model: share the MHD force-density kernel (6th/last) (#572)
krystophny Jul 8, 2026
99d2e45
Fix current density in magnetic field visualization example (#607)
WenyinWei Jul 8, 2026
ca01569
enzyme: exact Hessian of the composed local force map (#573)
krystophny Jul 8, 2026
8881635
ideal_mhd_model: share the hybrid lambda-force kernel (#574)
krystophny Jul 8, 2026
f36757d
ideal_mhd_model: share the constraint-force kernels (#575)
krystophny Jul 8, 2026
9de6072
Merge fast_poloidal and fast_toroidal Fourier bases into one template…
CharlesCNorton Jul 8, 2026
902d930
pybind: expose the unpreconditioned internal-basis gradient (#577)
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
f0cb35a
Make raw VMEC forces history independent (#626)
krystophny Jul 10, 2026
c7d93d0
Merge remote-tracking branch 'upstream/main' into HEAD
krystophny Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
188 changes: 188 additions & 0 deletions .github/actions/run-benchmarks/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
name: Run VMEC++ benchmarks
description: >
Install VMEC++ and run the benchmark suites in a fixed environment, then store
or compare the results. Two suites run here: the Python pytest end-to-end
benchmarks and the C++ Google Benchmark function-level microbenchmarks. This is
the single source of truth for the benchmark environment so the pull-request
and main/docs runs cannot drift apart; the only intended difference between
callers is whether results are written back to the benchmark-runs branch
(auto-push) and whether a regression is commented on the PR (comment-on-alert).

inputs:
auto-push:
description: "Push results to the benchmark-runs branch (main/docs run)."
default: "false"
comment-on-alert:
description: "Comment on the PR when a regression exceeds the alert threshold."
default: "false"
github-token:
description: "Token used to push results and/or comment on alerts."
required: true

runs:
using: composite
steps:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install system dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake libnetcdf-dev liblapack-dev libomp-dev libhdf5-dev

- name: Mount bazel cache
uses: actions/cache@v4
with:
path: "~/.cache/bazel"
key: bazel-bench

- name: Install vmecpp with benchmark dependencies
shell: bash
run: |
pip install uv
uv pip install --system -v .[benchmark]

# --- Python end-to-end benchmarks (pytest-benchmark) ---

- name: Run Python benchmarks
shell: bash
run: |
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
# by integration'. The benchmarks still run above; only the write-back is
# skipped for forks. Main/docs runs (auto-push) always store.
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: "customSmallerIsBetter"
output-file-path: benchmark_results_seconds.json
gh-pages-branch: benchmark-runs
benchmark-data-dir-path: benchmarks
alert-threshold: "150%"
fail-on-alert: false
auto-push: ${{ inputs.auto-push }}
comment-on-alert: ${{ inputs.comment-on-alert }}
github-token: ${{ inputs.github-token }}

# --- C++ function-level microbenchmarks (Google Benchmark) ---
#
# 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 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.

- name: Run C++ microbenchmarks
shell: bash
run: |
set -euo pipefail

TARGETS=(
"//vmecpp/vmec/ideal_mhd_model:fft_toroidal_bench"
"//vmecpp/vmec/ideal_mhd_model:dealias_constraint_force_bench"
"//vmecpp/free_boundary/laplace_solver:laplace_solver_bench"
"//vmecpp/vmec/output_quantities:output_quantities_bench"
)

OUT_FILE="${GITHUB_WORKSPACE}/cpp_benchmark_results.json"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "${TMP_DIR}"' EXIT

cd src/vmecpp/cpp

# --config=perf: optimized build with frame pointers and symbols
# retained, matching the existing .bazelrc convention for
# performance work.
bazel build --config=perf -- "${TARGETS[@]}"

# Run each target. `bazel run` so runfiles (e.g. test_data for the
# output-quantities benchmark) are available in the working
# directory. --benchmark_min_time keeps CI runtime bounded while
# still averaging enough iterations for a stable measurement.
for target in "${TARGETS[@]}"; do
name="${target##*:}"
bazel run --config=perf -- "${target}" \
--benchmark_min_time=0.2s \
--benchmark_format=json \
--benchmark_out="${TMP_DIR}/${name}.json"
done

python3 - "${TMP_DIR}" "${OUT_FILE}" <<'PY'
import glob
import json
import os
import sys

tmp_dir, out_file = sys.argv[1], sys.argv[2]
merged = {"context": None, "benchmarks": []}
skipped = 0
for path in sorted(glob.glob(os.path.join(tmp_dir, "*.json"))):
with open(path) as f:
data = json.load(f)
if merged["context"] is None:
merged["context"] = data.get("context")
for bench in data.get("benchmarks", []):
if bench.get("error_occurred"):
skipped += 1
continue
merged["benchmarks"].append(bench)

with open(out_file, "w") as f:
json.dump(merged, f, indent=2)

print(f"Merged {len(merged['benchmarks'])} benchmark entries "
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.
#
# Runs sequentially after the Python store step (composite-action steps
# are serial), so the two auto-push writes to benchmark-runs never race.
# A distinct suite name keeps the C++ results under their own key in
# data.js (window.BENCHMARK_DATA.entries) so they never overwrite the
# Python "Benchmark" suite, even though both share benchmark-data-dir-path.
#
# skip-fetch-gh-pages: the Python store step above already fetched the
# benchmark-runs branch into a local ref and (in compare mode) advanced
# it, so a second `git fetch benchmark-runs:benchmark-runs` here is
# rejected as non-fast-forward. Reuse the already-fetched local ref
# instead of re-fetching.
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:
name: "C++ Microbenchmarks"
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
alert-threshold: "150%"
fail-on-alert: false
auto-push: ${{ inputs.auto-push }}
comment-on-alert: ${{ inputs.comment-on-alert }}
github-token: ${{ inputs.github-token }}
39 changes: 5 additions & 34 deletions .github/workflows/benchmarks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake libnetcdf-dev liblapack-dev libomp-dev libhdf5-dev

- name: Install vmecpp with benchmark dependencies
run: |
pip install uv
uv pip install --system -v .[benchmark]

# Shared environment + run; PR runs compare and comment but never write
# back (auto-push stays false).
- name: Run benchmarks
run: |
export OMP_WAIT_POLICY=active
pytest benchmarks/test_benchmarks.py --benchmark-json=benchmark_results.json

- name: Compare 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
# by integration'. The benchmarks still run above; only the write-back
# is skipped for forks.
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
uses: benchmark-action/github-action-benchmark@v1.21.0
uses: ./.github/actions/run-benchmarks
with:
tool: "pytest"
output-file-path: benchmark_results.json
gh-pages-branch: benchmark-runs
benchmark-data-dir-path: benchmarks
auto-push: false
comment-on-alert: true
alert-threshold: "150%"
fail-on-alert: false
auto-push: "false"
comment-on-alert: "true"
github-token: ${{ secrets.GITHUB_TOKEN }}
8 changes: 5 additions & 3 deletions .github/workflows/clang_tidy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ jobs:
apt_packages: libhdf5-dev, liblapack-dev, libnetcdf-dev, gfortran, python3-dev, libomp-dev
build_dir: build
cmake_command: cmake -B build && cmake -DCMAKE_EXPORT_COMPILE_COMMANDS:STRING=ON build
# bazel-built test files have no cmake compile_commands entry, so
# clang-tidy cannot resolve <gtest/gtest.h>; exclude them from review
exclude: "./src/vmecpp/cpp/bazel-*,./src/vmecpp/cpp/external,./src/vmecpp/cpp/third_party,*_test.cc"
# bazel-built test and benchmark files have no cmake compile_commands
# entry, so clang-tidy cannot resolve <gtest/gtest.h> /
# <benchmark/benchmark.h> (both are Bazel-only deps); exclude them
# from review
exclude: "./src/vmecpp/cpp/bazel-*,./src/vmecpp/cpp/external,./src/vmecpp/cpp/third_party,*_test.cc,*_bench.cc"
# turn off "LGTM" comments: we only want pings about warnings/errors
lgtm_comment_body: ""
# If there are any comments, fail the check
Expand Down
31 changes: 4 additions & 27 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,13 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake libnetcdf-dev liblapack-dev libomp-dev libhdf5-dev

- name: Install vmecpp with benchmark dependencies
run: |
pip install uv
uv pip install --system -v .[benchmark]

# Shared environment + run; main/docs runs write results back to the
# benchmark-runs branch (auto-push) so they become the PR baseline.
- name: Run benchmarks
run: |
pytest benchmarks/test_benchmarks.py --benchmark-json=benchmark_results.json

- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1.21.0
uses: ./.github/actions/run-benchmarks
with:
tool: "pytest"
output-file-path: benchmark_results.json
gh-pages-branch: benchmark-runs
benchmark-data-dir-path: benchmarks
alert-threshold: "150%"
fail-on-alert: false
auto-push: "true"
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true

build-docs:
if: always()
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
9 changes: 0 additions & 9 deletions .github/workflows/test_bazel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,6 @@ jobs:
run: |
cd src/vmecpp/cpp
bazel build --config=${{ matrix.config }} -- //...
- uses: actions/checkout@v4
with:
repository: proximafusion/vmecpp_large_cpp_tests
path: src/vmecpp/cpp/vmecpp_large_cpp_tests
lfs: true
- name: Build VMEC++ C++ tests via bazel
run: |
cd src/vmecpp/cpp
bazel build --config=${{ matrix.config }} -- //vmecpp_large_cpp_tests/...
- name: Run full C++ test suite
run: |
cd src/vmecpp/cpp
Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/test_bazel_tsan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ jobs:
IMAGE=ghcr.io/${{ github.repository_owner }}/vmecpp-tsan:latest
docker pull "${IMAGE,,}"
docker tag "${IMAGE,,}" proximafusion/tsan
- uses: actions/checkout@v4
with:
repository: proximafusion/vmecpp_large_cpp_tests
path: src/vmecpp/cpp/vmecpp_large_cpp_tests
lfs: true
- name: Mount bazel cache
uses: actions/cache@v4
with:
Expand Down
Loading
Loading