The Open FUSION Toolkit (OFT) is a scientific computing suite for plasma and fusion research. It provides finite element methods on unstructured 2D/3D meshes for MHD equilibrium, stability, and time-dependent simulations.
The codebase is a hybrid Fortran/C/C++/Python project:
- Core computational code: Fortran 90 (
.F90files) with C/C++ bridge files - Python interface:
ctypes-based wrappers calling into compiled Fortran shared libraries - Build system: CMake (requires ≥ 3.27), orchestrated by
src/utilities/build_libs.py
| Tool | Purpose | Key Fortran Source | Python Module |
|---|---|---|---|
| TokaMaker | Axisymmetric Grad-Shafranov MHD equilibria | src/physics/grad_shaf*.F90 |
OpenFUSIONToolkit.TokaMaker |
| ThinCurr | Thin-wall eddy current modeling | src/physics/thin_wall*.F90 |
OpenFUSIONToolkit.ThinCurr |
| Marklin | 3D force-free ideal MHD equilibria | src/physics/taylor.F90 |
OpenFUSIONToolkit.Marklin |
| MUG | Time-dependent extended MHD | src/physics/xmhd*.F90 |
(Fortran executables only) |
src/
├── base/ # Core runtime (I/O, XML, sorting, stitching)
├── grid/ # Mesh types, readers (Cubit, Gmsh, native, T3D), multigrid
├── fem/ # Finite element basis functions and operators (H1, Hcurl, Lagrange)
├── lin_alg/ # Linear algebra (native solvers, PETSc, ARPACK, SuperLU, UMFPACK)
├── physics/ # Physics modules (Grad-Shafranov, thin-wall, Taylor, xMHD, tracing)
├── bin/ # Standalone Fortran executables
├── python/
│ ├── OpenFUSIONToolkit/ # Python package (ctypes wrappers)
│ │ ├── TokaMaker/ # TokaMaker Python API
│ │ ├── ThinCurr/ # ThinCurr Python API
│ │ └── Marklin/ # Marklin Python API
│ └── wrappers/ # Fortran-side C-interop wrapper subroutines
├── ext_libs/ # Bundled 3rd-party sources (triangle, minpack, bvls, dlsode)
├── tests/ # Regression tests (pytest-driven, Fortran + Python)
├── examples/ # Jupyter notebook examples per tool
├── utilities/ # Build scripts, code generators, helper tools
├── docs/ # Doxygen documentation sources
├── cmake/ # CMake find-modules for external dependencies
├── include/ # C/Fortran header files
└── CMakeLists.txt # Top-level CMake configuration
Detailed, task-specific procedures live in agent skills under .agents/skills/
(symlinked to .claude/skills/). Read the relevant SKILL.md before doing the task:
| Skill | Use it to |
|---|---|
oft-build |
Locate or set up a build tree, build external libraries, configure/compile/install OFT, build against system libraries in a sandbox, or change Python packaging (make_package.sh.in/pyproject.toml.in, which supersede the lint-only src/python/pyproject.toml) |
oft-test |
Run, select, debug, or write regression tests |
OFT uses a two-stage build: src/utilities/build_libs.py builds external libraries and
generates config_cmake.sh, which runs CMake (≥ 3.27) to create the build tree; then
make and make install. Full instructions are in the oft-build skill.
The build tree is created by the user and its location is not fixed by the
project. These instructions use <build_dir> for the configured build directory and
<config_dir> for its parent (holding config_cmake.sh, the compiled external
libraries, and install_release/; build_debug/install_debug for Debug configurations). Do not assume builds/build_release.
- User environments (the normal case): several build trees side by side, one per toolchain/parallelism combination, eg.
builds/GCC/MPI/build_releaseandbuilds/LLVM/build_release. - CI/ephemeral environments: a single flat tree —
<config_dir>isbuilds/,<build_dir>isbuilds/build_release.
Find candidates with find builds -maxdepth 4 -name config_cmake.sh. If more than one
is found, ask the user which to use rather than guessing. If none is found (eg. in a git
worktree, which never has builds/), follow the oft-build skill rather than starting a
new build unasked.
Building and testing need a Python environment with pytest, numpy, scipy, h5py,
netcdf4, and matplotlib.
- User environments: there is no project-managed virtual environment;
oft_venv/andsetup_env.share gitignored and will not exist in a normal checkout. If either is present anyway, it was made by hand: do not source it without asking, and check that the venv it activates still exists. If the user has not already specified which interpreter or environment to use, ask them — do not fall back to the systempython3. Carry the answer forward for the rest of the session. - CI/ephemeral environments only: the workflows create
oft_venv/and asetup_env.shthat activates it.source setup_env.shis valid there, and only there.
Tests use pytest and live in src/tests/ in subdirectories matching the source
layout (base/, grid/, lin_alg/, fem/, physics/), pairing test_*.py files with
the test_*.F90 programs they drive. They run from <build_dir> (make test, or
from <build_dir>/tests, ./run_test.sh <file> -k <name> for targeted runs; the file
path is resolved against the current directory), never from the source tree. See the oft-test skill for running, markers, debugging, and writing tests.
Always set OMP_NUM_THREADS when running tests: 1 if only one or two cores are
available, otherwise 2, regardless of core count. The only exception is running
individual tests specifically to study OpenMP thread scaling or performance.
Python code is linted with ruff. Configuration is in src/python/pyproject.toml (superseded by src/python/pyproject.toml.in for python packaging):
cd src/python && ruff checkRules: Pyflakes (F) + pycodestyle (E) with ignores for E722, F403, F405. Target: Python 3.7.
A custom lint checks Fortran debug stack entries:
cd src && python utilities/generate_stack.py -lThis validates that all SUBROUTINE/FUNCTION entries have matching debug stack annotations. Run from the src/ directory.
| Workflow | File | Trigger | Purpose |
|---|---|---|---|
| CI Build | ci_build.yaml |
push to main, PRs | Full matrix build (GCC, Intel, macOS) × (OpenMP, MPICH, OpenMPI) |
| Lint | lint.yaml |
push to main, PRs | ruff check + Fortran stack check |
| Coverage | cov_build.yaml |
push to main | Build with --coverage, upload to Codecov |
| CD Build and Deployment | cd_combined.yaml |
push to main, pypi_release, PRs | Release and nightly package/PyPI builds (deploy from pypi_release) |
| Container CD Build and Deployment | container_cd.yaml |
push to main, container_release, PRs | Container release builds (deploy from container_release) |
| GitHub Pages Build | website.yml |
push to main, gh-pages, PRs | Documentation/website build (deploy from gh-pages) |
| Copilot Agent Setup | copilot-setup-steps.yml |
manual/PR | Copilot agent environment setup |
| Copilot Review Setup | copilot-code-review.yml |
manual/PR | Copilot code review agent environment setup |
A representative build environment that can be used for creating a testing sandbox as used in copilot-setup-steps.yml is as follows:
- OS: Ubuntu 24.04
- Compilers:
gcc-14/g++-14/gfortran-14 - Parallel: OpenMP + MPICH (MPI enabled)
- Libraries: OpenBLAS, UMFPACK, SuperLU, ARPACK, HDF5, METIS
- Free-form Fortran 90+ (
.F90extension, preprocessed) - SPDX license header:
! SPDX-License-Identifier: LGPL-3.0-only - Doxygen-style comments with
!>and!!markers - Module names typically prefixed with
oft_(e.g.,oft_gs,oft_la_base) - Line length: unlimited (
-ffree-line-length-none), but keep reasonable - New functions/subroutines must have Doxygen documentation — this applies to code Doxygen actually processes; see "Test and example code" below
- All fortran keywords, statements, and built-in functions should be in UPPERCASE
- New functions should use
RESULTstyle definition instead of leading variable style INTEGERandREALdefinitions should explicitly provide theKIND/precision using one ofi4/i8orr4/r8(defined insrc/base/local.F90) respectively- Two space indentation should be used, with the exception of subroutine/function bodies
- SPDX license header comment block at top of each file
- New functions/subroutines must have Doxygen documentation using docstrings with
@-style commands (eg.@param,@result) — this applies to code Doxygen actually processes; see "Test and example code" below - Target Python 3.7+ (for OMFIT compatibility)
- Use
ctypesfor Fortran interop; wrapper patterns in_interface.pyfiles - All Python wrappers live under
src/python/OpenFUSIONToolkit/ - Balance readability and efficiency; For example, avoid overly long or nested list comprehensions and inline if statements
- Four space indentation should be used
Doxygen's INPUT list in src/docs/Doxyfile.in covers src/docs, base, grid,
lin_alg, fem, physics, bin, and python. Neither src/tests/ nor
src/examples/ is scanned directly, but examples still reach the documentation
indirectly: the build_ex_docs target runs src/utilities/generate_doc.py, which
reads examples/*.F90, examples/*/*/*.F90, and examples/*/*/*.ipynb and writes
docs/generated/doc_*.md, which Doxygen then picks up through its docs entry.
- Example sources are documentation. Fortran examples under
src/examples/carry their own!!comment format, including Doxygen page anchors and linking commands (eg.!!API Example 1 ... {#doc_api_ex1},\section,\ref), whichgenerate_doc.pyparses into published pages. The same applies to the example notebooks. Do not strip or "correct" this markup, and follow the surrounding file when extending it. - Everything else under
src/tests/andsrc/examples/is not parsed into the API documentation, so its functions do not need Doxygen-style documentation — no!>/!!function blocks in Fortran, no@-style commands (@param,@result,@brief) in Python docstrings. Plain comments and plain docstrings are fine. - Doxygen linking commands (
\section,\ref, …) remain valid anywhere and may be used where they help, even in files whose functions need no documentation blocks. - Function-level documentation blocks in test code are optional.
- Brief documentation is still helpful and welcome: a short line saying what a test covers or what an example demonstrates aids the next reader.
When reviewing changes, do not flag missing documentation on test functions, and do not request Doxygen-style function documentation for files outside the paths Doxygen processes.
- PRs for a specific tool should be titled:
ToolName: description(e.g.,TokaMaker: Fix boundary condition) - Keep changes focused on a single feature; secondary changes should be minimal
- Comment on whether APIs or input files change
- Run regression tests before submitting
-
Single OFT_env instance: The Python
OFT_envclass enforces a singleton. Tests usemultiprocessing.Processto work around this. Never create twoOFT_envinstances in the same process. -
Tests run from build tree: Tests must be run from
<build_dir>/tests/(or viamake testfrom<build_dir>), not from the source tree, because compiled Fortran test executables are in the build tree. See "Locating the build tree" —<build_dir>is onlybuilds/build_releasein CI/ephemeral environments. -
ext_libs/ is bundled third-party code: Do not modify files in
src/ext_libs/. These are upstream sources (triangle, minpack, bvls, dlsode). -
Fortran/Python interop: The Python package calls compiled Fortran via
ctypes. The Fortran-side wrappers are insrc/python/wrappers/and useISO_C_BINDING. Changes to Fortran function signatures require corresponding updates to bothwrappers/*_f.F90andpython/OpenFUSIONToolkit/*/_interface.py. -
CMake template files: Some files use
@VARIABLE@CMake substitution (e.g.,__init__.py,pyproject.toml.in,run_test.sh.in). Edit the.intemplate, not the generated file. -
Build trees compile an absolute source path:
config_cmake.shends with the absolute path of thesrc/it builds, so in a git worktree or second checkout an existing build tree usually compiles the main checkout, not your changes. See "Check which source tree it builds" in theoft-buildskill.