Coding Standard¶
This standard records the conventions used in SIMPLE. Rules marked (new code) are not yet met everywhere in the existing sources; they apply to new code and to code that a pull request rewrites. Do not reformat or restructure unrelated code in the same pull request: a formatting-only or modernisation change is its own pull request (tier T1, golden records unchanged).
Fortran¶
Language and files¶
Modern free-form Fortran with lower-case keywords, compiled with GNU Fortran. Code on the OpenACC paths must also build with NVIDIA Fortran (
make nvfortran,make nvfortran-acc). Native CUDA requires the CUDA toolkit and either GNU or NVIDIA Fortran; see the GPU build instructions.All Fortran sources pass through the preprocessor (
-cpp), whatever the file extension.The GNU build uses
-Wall -Wextra -Wimplicit-interfaceand rejects lines longer than 132 characters. Existing code emits warnings; a change must not add new ones.One module per file, with the file named after the module (new code).
Modules and declarations¶
Every procedure lives in a module; no new external procedures or
includefiles (new code).Each module starts with
usestatements, thenimplicit none, thenprivatewith an explicitpubliclist for new modules.Use
use module, only: namefor every module dependency (new code). Rename on import to avoid clashes, for exampleuse field_can_mod, only: eval_field => evaluate.Every dummy argument has an explicit
intent. Most existing dummy arguments comply; new and changed procedures have no exceptions.Declarations come at the start of the scope.
No
saved state or module variables for data that belongs to one particle; per-particle state goes into a derived type that the caller owns (new code).
Kinds and numbers¶
Real numbers are
real(dp)withuse, intrinsic :: iso_fortran_env, only: dp => real64(new code). Some older modules usedp = kind(1.0d0)ordouble precision; convert them in a separate T1 pull request.Literal constants carry the kind:
1.0_dp,0.5_dp.Use
twopiand other shared constants fromutilinstead of new local copies.Do not use the IEEE intrinsic modules (
ieee_arithmetic,ieee_exceptions) in library sources; they make gfortran save and restore the floating-point environment around procedure calls. Useis_finiteandquiet_nanfromfinite_check. Tests may use the IEEE modules as an oracle.
Derived types¶
Type names end in
_t(all current types do), for exampletracer_t,field_can_t,symplectic_integrator_t.Extend abstract types only, such as
magnetic_field_torcoordinate_scaling_t. To reuse a concrete type, hold it as a component and delegate (new code).Use
allocatablecomponents and variables instead ofpointer. Transfer ownership withmove_alloc. Usepointeronly where the target must be shared or associated, for example procedure pointers for field selection.
Control flow and logical expressions¶
Fortran does not guarantee short-circuit evaluation of
.and.and.or.. A guard that protects an index, an allocation, an association or an optional argument must be a separateif:if (present(ierr)) then if (ierr /= 0) return end if
not
if (present(ierr) .and. ierr /= 0) return.Stop on unrecoverable input or configuration errors with
error stopand a message that names the input key (new code). Report recoverable numerical failures through anierrargument or an exit code.
Performance¶
No allocation or deallocation inside the per-step integration loop. Allocate work arrays once per particle or per thread, as
simple_maindoes for the trajectory buffers.Loop with the leftmost array index innermost.
No I/O inside the tracing loop except where output is the purpose; guard debug output with the
SIMPLE_ENABLE_DEBUG_OUTPUTpreprocessor switch.Measure before and after any performance change and report the result in the pull request. Output must stay unchanged unless the pull request says otherwise.
OpenMP and accelerators¶
Particles are traced in parallel, one marker per loop iteration, in
!$omp parallelregions with explicitprivateandfirstprivatelists. Preferdefault(none)ordefault(shared)with explicit private lists in new regions.Module variables that hold per-thread working state are declared
!$omp threadprivate. Prefer passing state in derived types for new code.Shared counters use
!$omp atomic; use!$omp criticalonly for output and other non-numerical serialisation.Results must not depend on the thread count beyond floating-point rounding. Seeded runs with
deterministic = .True.must be reproducible.Procedures called from OpenACC kernels carry
!$acc routine seqand must not use procedure pointers orclass()polymorphism in the hot path (seeorbit_rk_core). The CPU path must stay unchanged when acceleration is disabled.
Formatting¶
Four-space indentation and lines of at most 88 characters, as configured in
.fprettify. Some older files use two-space indentation or longer lines; keep the style of the surrounding code in small edits and do not reformat whole files in a functional change.Format new files with
fprettify -c .fprettify <file>. fprettify does not read.fprettifyby itself (it looks for.fprettify.rc); without-cit indents by three spaces. In existing files, inspectfprettify -c .fprettify --diff <file>and apply only the hunks that touch your change. Do not usefprettify --enable-replacementstogether with a line-length option; it can split tokens across continuation lines.CI checks changed Fortran lines with
fprettify -c .fprettify; runpython tools/check_changed_lines.pybefore pushing.One space before an inline comment. Use
!>for documentation comments on modules, types and procedures.
Generated code¶
The physics kernels in src/ are handwritten. CMake generates the version
module in the build tree from src/version.f90.in. Prototypes under
concept/ generate kernels at build time from symbolic derivations. If
generated kernels are added to src/, check in both the generator and its
output, mark the output as generated and do not edit it by hand (new code).
Python¶
Python code is the pysimple package (python/pysimple/), the tools in
tools/, the examples and the tests in test/python/.
CI uses Python 3.12. Start modules with a docstring and
from __future__ import annotations(new code).Follow PEP 8 with a line length of 88 characters. CI runs
ruff checkandruff formaton changed lines inpython/andtools/(tools/check_changed_lines.py).Public functions have type hints and a docstring in plain prose that names units, shapes and raised exceptions (new code). The API reference is built from the package docstrings with Sphinx (
docs/).pysimpleis a thin layer oversimple_apiandparams. Physics belongs in Fortran so thatsimple.xandpysimplegive the same result.Use
pathlib.Pathfor paths andnumpyarrays for state data. States are(s, theta, phi, v/v0, v_par/v)with shape(5,)or(5, n).Tests use pytest. Use
pytest.importorskipfor optional dependencies and skip, not fail, when optional external data is missing.Tools that convert external data, such as
tools/gvec_to_boozer_chartmap.py, document the input and output conventions and the file schema they write.
Shell, CMake and build files¶
Shell scripts start with
#!/bin/bashor#!/usr/bin/env bashand are executable (checked by the pre-commit hooks in.pre-commit-config.yaml).New tests are registered with
add_testand carry labels (unit,integration,system,python,slow,regression) so that themaketest targets select them.cmake/dependency_versions.jsonpins fortnum, fortplot and the golden-record libneo revision. Normal libneo builds select the matching branch, falling back tomain, unlessLIBNEO_REForLIBNEO_PATHis supplied. Dependency changes are tier T4.
Comments and names¶
Comments state purpose, units, coordinate conventions and non-obvious numerics. History belongs in commit messages and
CHANGELOG.md.Angles are in radians and are not wrapped during integration. State whether a radial variable is
s,sqrt(s)orrhowherever it crosses an interface.Names are lower case with underscores. Physical symbols may follow the notation of the referenced paper when the comment cites it.