VMEC++ is a Python-friendly, from-scratch reimplementation in C++ of the Variational Moments Equilibrium Code (VMEC), a free-boundary ideal-MHD equilibrium solver for stellarators and tokamaks.
The original version was written by Steven P. Hirshman and colleagues in the 1980s and 1990s.
The latest version of the original code is called PARVMEC
and is available here.
Compared to its Fortran predecessors, VMEC++:
- has a zero-crash policy and reports issues via standard Python exceptions
- allows hot-restarting a run from a previous converged state (see Hot restart)
- 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
- typically runs just as fast or faster
- comes with substantial documentation of its internal numerics
VMEC++ can run on a laptop, but it is a suitable component for large-scale stellarator optimization pipelines.
On the other hand, some features of the original Fortran VMEC are not available in VMEC++. See below for more details.
- Usage
- Installation
- Hot restart
- Differences with respect to PARVMEC/VMEC2000
- Roadmap
- Related repositories
- License
This is a quick overview of the three main ways in which you can use VMEC++.
See examples/ for some actual example scripts.
Suitable input files are found in examples/data
.
If unsure where to start, we suggest to give the w7x
case a try, which is a five-field-period stellarator case for the Wendelstein 7-X stellarator.
VMEC++ offers a simple Python API:
import vmecpp
# construct a VmecInput object, e.g. from a classic Fortran input file
vmec_input = vmecpp.VmecInput.from_file("input.w7x")
# run VMEC++
vmec_output = vmecpp.run(vmec_input)
# inspect the results or save them as a classic wout file
vmec_output.wout.save("wout_w7x.nc")
Note that other output files are planned to be accessible via members of the output
object called threed1
, jxbout
and mercier
soon.
SIMSOPT is a popular stellarator optimization framework. VMEC++ implements a SIMSOPT-friendly wrapper that makes it easy to use it with SIMSOPT.
import vmecpp.simsopt_compat
vmec = vmecpp.simsopt_compat.Vmec("input.w7x")
print(f"Computed plasma volume: {vmec.volume()}")
You can use VMEC++ directly as a CLI tool. In a terminal in which Python has access to the VMEC++ package:
# run on a given input file -> produce corresponding wout_w7x.nc
python -m vmecpp examples/data/input.w7x
# check all options
python -m vmecpp --help
See docker/README.md.
Ubuntu 22.04 and 24.04 are both supported.
- Install required system packages:
sudo apt-get install build-essential cmake libnetcdf-dev liblapacke-dev libopenmpi-dev libeigen3-dev nlohmann-json3-dev libhdf5-dev
- Install VMEC++ as a Python package (possibly after creating a dedicated virtual environment):
pip install git+https://github.com/proximafusion/vmecpp
The procedure will take a few minutes as it will build VMEC++ and some dependencies from source.
A common issue on Ubuntu is a build failure due to no python
executable being available in PATH, since on Ubuntu the executable is called python3
.
When installing in a virtual environment (which is always a good idea anyways) python
will be present.
Otherwise the Ubuntu package python-is-python3
provides the python
alias.
- Install dependencies via Homebrew:
brew install [email protected] gcc cmake ninja libomp netcdf-cxx eigen nlohmann-json protobuf git open-mpi
- Install VMEC++ as a Python package (possibly after creating a virtual environment):
# tell cmake where to find gfortran and gcc as they have non-standard names
export FC=$(which gfortran-14)
export CC=$(which gcc-14)
python3.10 -m pip install git+https://github.com/proximafusion/vmecpp
VMEC++ is currently not packaged for conda, but all its dependencies are and VMEC++
can be installed inside a conda environment. An example environment.yml
file is
provided here that
can be used, after cloning the vmecpp
repository, as:
git clone --recurse-submodules [email protected]:proximafusion/vmecpp
cd vmecpp
# this creates a "vmecpp" conda environment
conda env create --file environment.yml
# use the environment as usual
conda activate vmecpp
After having installed the build dependencies as shown above, you can compile the C++ core of VMEC++ via CMake or Bazel. E.g. with CMake:
git clone --recurse-submodules [email protected]:proximafusion/vmecpp
cd vmecpp
cmake -B build # create and configure build directory
cmake --build build --parallel # build VMEC++
# you can now use the vmec_standalone C++ executable to run VMEC on a VMEC++ input JSON file, e.g.
./build/vmec_standalone ./examples/data/solovev.json
The main C++ source code tree is located at src/vmecpp/cpp/vmecpp
.
By passing the output of a VMEC++ run as initial state for a subsequent one, VMEC++ is initialized using the previously converged equilibrium. This can dramatically decrease the number of iterations to convergence when running VMEC++ on a configuration that is very similar to the converged equilibrium.
import vmecpp
input = vmecpp.VmecInput.from_file("w7x.json")
# Base run
output = vmecpp.run(input)
# Now let's perturb the plasma boundary a little bit...
input.rbc[0, 0] *= 0.8
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:]
# We can now run with hot restart:
# passing the previously obtained 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)
When developing the C++ core, it's advisable to locally run the full C++ tests for debugging or to validate changes before submitting them. The full tests are not stored in the sources of this repo, but in a separate repo: https://github.com/proximafusion/vmecpp_large_cpp_tests . See the instructions there for how to run those tests locally. The CI of this repo includes those tests too.
The single-thread runtimes as well as the contents of the "wout" file produced by VMEC++ can be compared with those of Fortran VMEC v8.52. 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.
VMEC++:
- reports issues via standard Python exceptions and has a zero crash policy
- allows hot-restarting a run from a previous converged state (see Hot restart)
- 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
- 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
- non-stellarator-symmetric terms (
lasym == true
) are not supported yet - free-boundary works only for
ntor > 0
- axisymmetric (ntor = 0
) free-boundary runs don't work yet lgiveup
/fgiveup
logic for early termination of a multi-grid sequence is not implemented yetlbsubs
logic in computing outputs is not implemented yetlforbal
logic for non-variational forces near the magnetic axis is not implemented yetlrfp
is not implemented yet - only stellarators/Tokamaks for now- several profile parameterizations are not fully implemented yet:
gauss_trunc
two_power_gs
akima_spline
akima_spline_i
akima_spline_ip
cubic_spline
cubic_spline_i
cubic_spline_ip
pedestal
rational
line_segment
line_segment_i
line_segment_ip
nice_quadratic
sum_cossq_s
sum_cossq_sqrts
sum_cossq_s_free
- some (rarely used) free-boundary-related output quantities are not implemented yet:
curlabel
- declared but not populated yetpotvac
- declared but not populated yetxmpot
- not declared yetxnpot
- not declared yet
- 2D preconditioning using block-tridiagonal solver (
BCYCLIC
) is not implemented; neither are the associated input fieldsprecon_type
andprec2d_threshold
- VMEC++ only computes the output quantities if the run converged
- The Fortran version falls back to fixed-boundary computation if the
mgrid
file cannot be found; VMEC++ (gracefully) errors out instead. - The Fortran version accepts both the full path or filename of the input file as well as the "extension", i.e., the part after
input.
; VMEC++ only supports a valid filename or full path to an existing input file.
Some of the things we are planning for VMEC++'s future:
- free-boundary hot-restart in Python
- open-sourcing the full VMEC++ test suite (including the Verification&Validation part that compares
wout
contents) - open-sourcing the source code to reproduce VMEC++'s performance benchmarks
- VMEC++ usable as a C++ bazel module
Some items we do not plan to work on, but where community ownership is welcome:
- packaging VMEC++ for other platforms or package managers (e.g. conda, homebrew, ...)
- native Windows support
- 2D preconditioner using
bcyclic_plus_plus
proximafusion/vmecpp-validation
- Validation tests for VMEC++proximafusion/vmecpp_large_cpp_tests
- Large C++ tests for VMEC++proximafusion/the_numerics_of_vmecpp
- Documentation of the numerical details of VMEC++
vmecpp
is distributed under the terms of the MIT license.