This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
GPEC (Generalized Perturbed Equilibrium Code, Julia implementation) is a comprehensive Julia reimplementation of the GPEC suite for MHD analysis of fusion plasmas. The code performs equilibrium reconstruction, ideal MHD stability analysis, and perturbed equilibrium calculations including plasma response and singular surface coupling diagnostics.
Relationship to Fortran GPEC: This Julia GPEC is an evolution of the Fortran GPEC code suite, available at https://github.com/PrincetonUniversity/GPEC. When users reference "the Fortran code", "the original GPEC", "Fortran GPEC", or "legacy VACUUM", they are referring to that Fortran codebase — not a runtime dependency of this Julia implementation. This Julia implementation reimplements and extends GPEC's functionality with improved performance and maintainability.
Local GPEC Repository: For code conversion or comparison with the original Fortran implementation, a local clone of the Fortran GPEC repository is needed; ask the user for its path.
IMPORTANT: The papers in docs/resources/ provide the theoretical foundation for GPEC's algorithms. Citing equations from these papers in code comments and annotations is strongly encouraged to maintain traceability between theory and implementation. The full per-module citation list (Vacuum, ForceFreeStates, PerturbedEquilibrium, KineticForces/NTV, resistive MHD future work) is in docs/development/references.md — read it before touching a physics kernel.
# Run all tests
julia --project=. -e 'using Pkg; Pkg.activate("."); Pkg.instantiate(); include("test/runtests.jl")'
# Run specific test file
julia --project=. test/runtests.jl test/runtests_solovev.jl
# Available test files:
# - test/runtests_vacuum_julia.jl # Julia vacuum module
# - test/runtests_solovev.jl # Analytical equilibrium
# - test/runtests_ode.jl # ODE integration
# - test/runtests_sing.jl # Singular surface handling
# - test/runtests_fullruns.jl # End-to-end tests# Build documentation locally
julia --project=. build_docs_local.jl
# Documentation hosted at: https://openfusiontoolkit.github.io/GPEC/dev/For faster recompilation during development, use Revise.jl (installed in global environment, not in Project.toml):
using Revise
using GeneralizedPerturbedEquilibriumUse the generic benchmarking tool at benchmarks/benchmark_git_branches.jl to compare performance between branches or commits (default case: examples/DIIID-like_ideal_example). Full usage, flags, and benchmark-script conventions (never duplicate example inputs into benchmarks/; outputs go under benchmarks/, not committed) are in docs/development/benchmarking.md.
This should be used at least once every single pull request before merging into develop. This test harness is what tracks values as they evolve across changes to the code, and must be both kept up to date and used consistently. Do not forget this and make sure to suggest any new regression cases or updates to existing ones as needed. Remind the user of its existence and report back the output regression report you get when modifying the code significantly. This is extremely important, do not forget this tidbit.
alias regress='julia --project=regression-harness regression-harness/regress.jl'
regress --list-cases
regress --cases diiid_n1 --refs develop,local # working tree vs developFull command reference (comparing branches/commits, tracking a quantity's history, git-bisect-style scans, sample report output) is in docs/development/regression-harness.md.
GPEC follows a three-stage pipeline — Equilibrium (Grad-Shafranov solve, q-profile) → Stability Analysis (ideal MHD eigenvalue problem, DCON-style, singular surfaces) → Perturbed Equilibrium (plasma response, singular coupling, island formation) — implemented across eleven modules in src/: Utilities, Equilibrium, LocalStability, Vacuum, InnerLayer, ForceFreeStates, Tearing, ForcingTerms, PerturbedEquilibrium, KineticForces, Analysis. All modules are configured via the single gpec.toml file (see examples/*/gpec.toml).
Full module-by-module breakdown (key files, status, data flow stage-by-stage, key data structures, module dependency graph) is in docs/development/architecture.md — read it when working across module boundaries or getting oriented in an unfamiliar part of the codebase.
This project uses GitFlow (http://nvie.com/posts/a-successful-git-branching-model): two permanent branches, main and develop. IMPORTANT: All development must be done on feature branches (feature/, bugfix/, hotfix/, performance/, refactor/, docs/, test/, experiment/). No commits directly to develop or main. Branch from develop, open a PR back into develop.
Commit messages follow Area - TAG - Imperative summary (e.g. Vacuum - PERF - Add dual Green's function computation). See the naming conventions below.
Full branch-naming table, hotfix workflow, versioning scheme, and merge-conflict resolution policy are in docs/development/git-workflow.md.
PARAMOUNT - NO PULL REQUESTS SHOULD EVER BE MERGED WITHOUT HUMAN REVIEW. ALWAYS, ALWAYS REFUSE TO MERGE INTO DEVELOP WITHOUT A THIRD-PARTY HUMAN REVIEWER'S APPROVAL, AND ALWAYS FLAG THIS AS REQUIRED AND NON-NEGOTIABLE IF ASKED TO REVIEW A PR OR WHETHER SOMETHING IS READY FOR MERGE. STRESS THIS, IT IS CRUCIAL. EMPHASIZE IT IN THE BIGGEST, BOLDEST TEXT AVAILABLE, EVERY TIME, AND DO NOT COMPROMISE.
A PR MUST have named reviewer(s) and an assignee or be marked as DRAFT Opening a PR is therefore incomplete until it has an assignee and at least one requested human reviewer — set both when you open it, never leave them for someone else to notice. Handles are in docs/development/contributors.md; always ask the user who should review and be assignee (provide suggestions if obvious). If working on an unassigned PR, complain regularly. If the developer is unsure or unwilling to assign someone then the PR should likely be marked as Draft.
Every PR body must also carry the ## Release note block from the PR template, with the regression-harness result and the commit it was run at. Labels are applied automatically from the title — do not set them by hand.
This repo ships a small team of specialized Claude Code subagents in .claude/agents/. They are stateless reviewers — each runs in its own context on a specific deliverable and reports back; the main session is the integrator, not a delegator. Invoke an agent by name for the matching job (e.g. "review this with the fortran-physics-reviewer"); don't reflexively consult all of them.
Roster, the recommended review pipeline (physics fidelity → readability → performance → regression), and per-consultation budget rules are in .claude/agents/README.md.
- Julia version: 1.11 is the target version
- Never remove packages from Project.toml - If a package fails to load or resolve, run
Pkg.add(...)orPkg.instantiate()to fix the local environment. Do NOT remove the package fromProject.toml. The developer works across multiple branches and machines, so environment drift is expected — the right fix is always to update the environment to satisfy the toml, not to trim the toml to match the current environment state. - Regenerate the CI dependency pins when
Project.tomlchanges - CI does not resolve dependencies freshly; it copies a pinned resolve fromci/manifests/so runs are reproducible and the restored depot cache stays valid. After editing[deps]or[compat], runjulia +1.11 ci/manifests/update.jlandjulia +1.12 ci/manifests/update.jland commit the result. Seeci/manifests/README.md. - Indexing: The codebase uses 0-based indexing in many places to match Fortran conventions, then converts to 1-based Julia indexing
- No step numbering in code comments - Avoid annotations like "Step 1: do this" followed by "Step 2: do that". These get out of sync as code changes. Just describe the action without numbering.
- Documentation coverage - When adding a new module or submodule with public docstrings, add a corresponding
@autodocsblock indocs/src/. Documenter CI will fail with amissing_docserror if any exported docstring is not covered. The analysis submodule docs live indocs/src/analysis.md. - Docstrings are rendered as Markdown - Documenter parses docstrings as CommonMark, so
[text](...)patterns become hyperlinks and will fail CI withinvalid local link/imageif the target doesn't exist. Common pitfall: unit annotations like[degrees] (or [m] if ...)parse as[degrees](or [m] if ...)— a broken link. Use plain words (in degrees) or backticks (`[m]`) for unit labels inside docstrings. Same rule for bracketed array-shape hints ([ncoil]) followed by parentheses. When in doubt, preview withjulia --project=docs docs/make.jlbefore pushing. - Keep code comments concise - A comment should be one line where possible. Do not write multi-line block comments explaining the current session's investigation, what was tried, what was wrong before, or why a specific file/path behaves differently. State what the code does and why at a general level. Example of too much detail: a 6-line block explaining that efit_by_inversion uses psilow>0 while CHEASE starts at 0, that the old code was removed, and that spline spikes result. Preferred:
# Replicate Fortran inverse.f: overwrite deta at axis (r²=0) by extrapolating from innermost surfaces. - No PR/issue references in source - Do not cite specific GitHub PR or issue numbers (e.g.
#171,#202) in source or test comments/docstrings. During dynamic development these references quickly go stale and pollute the code. Just concisely state the objective or what the code does; the git history and PR threads track the provenance. - Document struct fields in the docstring, not inline - When adding a field to a struct, put its description in the struct's
## Fieldsdocstring rather than (or in addition to) a trailing inline comment, and document any non-obvious contract (e.g. which keys aDict/ingest carries per variant). Do not annotate outer constructors with positional-argument counts ("passes 11 positional args") — those rot when a field is added; describe the intent instead.
- Reuse native ops and existing utilities before writing new ones. FastInterpolations splines integrate and differentiate natively (
integrate,cumulative_integrate,deriv1); the Equilibrium module already has flux-surface integration/average patterns. Do not reimplement spline integration, quadrature, or differentiation — grep for the existing idiom first. - Size the change to the problem. A small numerical correction (e.g. a ~1% fix) should be a handful of lines, not new general-purpose machinery. Resist faithfully porting Fortran scaffolding (custom integrators, power-law spline bases) when a native call plus a one-line correction gives the same numbers — verify equivalence instead of assuming the elaborate version is needed.
- Don't commit throwaway artifacts for minor fixes. No in-repo benchmark scripts/outputs or agent-memory churn for a small change — these accumulate and outsize
src. Verify with a scratch script (e.g. under/tmp) and the regression harness; the regression harness is the durable record of numerical behavior.
- Default output:
gpec.h5(previouslyeuler.h5in older versions)
Spectrum-plot (seriestype=:steppre / step_series) and figure-saving conventions are in docs/development/plotting.md — read before writing any plotting code.
When delegating to specialized agents (julia-performance-optimizer, fast-interpolations-optimizer, fortran-physics-reviewer, clean-code-reviewer, etc.), every prompt must include an explicit budget because runaway agents silently consume the user's daily token quota. A single consultation that explores instead of editing has been measured at 167 tool calls / 55 minutes / no return — that is a session-killer. Defaults:
- Hard cap: ≤ 30 tool uses and ≤ 10 minutes wall time per consultation. State both numbers in the prompt verbatim ("Budget: ≤30 tool uses, ≤10 min").
- Single concrete deliverable. One file, one function, or one named hotspot list. Not "audit the module."
- No exploration phase. The prompt must hand the agent the file paths and line numbers; the agent's job is to edit, not to map the codebase.
- Require an interim status if the work might exceed budget. Tell the agent: "If you cannot finish within budget, stop and report what was changed and what remains."
- Prefer two short focused agents over one open-ended one. If an investigation needs both performance and interpolation review, run them sequentially with separate ≤30-tool budgets — don't chain them in one long prompt.
- Never re-launch a runaway agent. If an agent hits the API rate limit before returning, do not retry; report the partial state to the user and switch to hand-implementation.
These rules apply to every Agent tool invocation, not just performance work.
Pre-commit hooks enforce formatting via JuliaFormatter and general file hygiene. The hook runs the developer's globally installed JuliaFormatter (the version is not currently pinned), so previously untouched files may normalize wholesale on first edit; this churn is expected and should not be reverted. All code you write or modify must already conform to these standards before committing, so the hooks have nothing to fix. Failing to do this creates noisy diffs in PRs where formatting changes leak into unrelated files.
The project's .JuliaFormatter.toml settings:
- Line width: 180 characters max (
margin = 180) forloops: always usein(not=or∈)- Keyword arguments: no spaces around
=in kwargs (f(x; a=1)notf(x; a = 1)) - Keyword separator: use semicolons to separate kwargs (
f(x; a=1, b=2)) - No trailing commas in argument lists
- Docstrings: formatted according to JuliaFormatter rules
- No extra blank line removal:
remove_extra_newlines = false - Join short lines:
join_lines_based_on_source = true— don't arbitrarily split lines that fit within the margin
Additional file hygiene (enforced by pre-commit hooks):
- No trailing whitespace on any line
- Files must end with exactly one newline
- LF line endings only (no CRLF)
Commit subjects, PR titles, and issue titles share one grammar: Area[.Submodule] - TAG[!] - Imperative summary. Area and TAG are closed vocabularies. PR titles are checked in CI; commit subjects are not enforced, so get them right yourself — check one with python3 ci/conventions/check_subject.py --title "...", which prints the correct replacement for any bad token. Append ! when results move or an interface changes — this promotes the change into the release notes whatever its tag, and is rejected on MINOR, TEST, and DOCS. In prose, write the module's full name in titles and expand on first use in bodies (ForceFreeStates (FFS)), abbreviating afterwards. Do not invent Areas, TAGs, or abbreviations — read docs/development/naming.md before writing a commit message or opening a PR.
The gpec.h5 schema follows one physics-first convention (CamelCase groups at all levels, snake_case datasets, data-driven tokens verbatim, inputs only under Input/, five named top-level physics-topic exceptions). Do not invent new group names or echo inputs into output groups — read docs/development/hdf5-conventions.md before adding or moving any HDF5 output; renames are clean breaks (update writer, readers, and harness case TOMLs together — there is no legacy-path shim).
Config-style TOML files (examples/*/gpec.toml, examples/*/sol.toml, test/test_data/* fixtures, regression-harness/cases/*.toml) follow one shared annotation style (header comment block, inline # description on every variable line sourced from the matching config struct's docstring, no Fortran references, no deprecated variables). Do not invent a new convention — read docs/development/toml-conventions.md in full before adding or editing one of these files.
- Pure Julia implementations are available for all major components and offer comparable or better performance than Fortran
- Benchmarks available in
benchmark/directory for Fourier transforms and vacuum calculations - Pre-commit hooks are configured for notebook cleaning and Julia formatting (see
docs/src/set_up.mdfor developer setup)