Architecture¶
This page maps the code: how a run flows through the modules, where fields, coordinates and integrators live, and what to change to add a field source or an integrator. Implementation detail of coordinates and fields is in Coordinate Systems and Magnetic Fields in SIMPLE; the equations are in Model and methods.
Run sequence¶
simple.x calls simple_main::main (src/simple_main.f90):
load_configreads&configinto moduleparamsand&profilesintosimple_profiles.init_runloads the field (init_field), derives the normalisation (params_init: \(v_0\), \(\rho_0\), step \(\Delta\tau\)) and initialises collisions.init_sampling_fieldandsample_startfillzstartaccording tostartmode(src/samplers.f90).init_tracing_fieldselects the integration field.trace_parallelrunstrace_orbitfor each marker in an OpenMP loop (schedule(dynamic, 1)); withgpu_mode = 'production',trace_gpu_productionreplaces it.write_outputwrites the text files andresults.nc.
simple_api (src/simple_api.f90) exposes the same steps as separate
calls (simple_init, simple_sample, simple_trace, …) plus
single-orbit diagnostics. Public states are
\((s, \vartheta, \varphi, v/v_0, \lambda)\) in reference coordinates;
trace_orbit converts them with ref_to_integ before integration.
Module map¶
Module (file) |
Role |
|---|---|
|
|
|
run sequence, per-orbit loop, wall and LCFS events, recovery, output |
|
|
|
start positions; fusion-weighted births |
|
|
|
field classes (coils, Boozer chartmap, splined) and canonical
fields ( |
|
canonical field dispatch, |
|
non-canonical field evaluation for RK45 and sampling |
|
reference coordinate system |
|
symplectic and quasi-symplectic steppers, LCFS event location |
|
RK45 guiding-centre equations ( |
|
full-orbit Boris pusher |
|
SPECTRE tracing per volume, full orbit near interfaces, field lines |
|
Coulomb collision operator ( |
|
orbit classification |
|
STL wall segment test through CGAL |
|
bounce and toroidal frequencies; invariants to starting states |
|
|
|
OpenACC and native CUDA tracing for Boozer fields |
|
Python layer over |
Libraries: libneo supplies VMEC splines, Boozer conversion, coordinate
systems (libneo_coordinates), the field base class magnetic_field_t,
Biot–Savart, SPECTRE fields and the ODE and spline routines; fortplot draws
diagnostic plots; fortnum provides the root finder and the GPU RK5(4)
integrator. Versions are pinned in
cmake/dependency_versions.json.
Fields and coordinates¶
Three layers separate field data from the coordinates used for integration.
Field objects extend libneo
magnetic_field_tand implementevaluate(x, Acov, hcov, Bmod)in their own coordinates:vmec_field_t(libneo),coils_field_t,boozer_chartmap_field_t,splined_field_t(any field resampled on reference coordinates) andspectre_field_t(libneo).field_from_filechooses the class from the file.Reference coordinates (
ref_coords, a libneocoordinate_system_t) are VMEC \((s,\vartheta,\varphi)\) or a chartmap \((\rho,\vartheta,\zeta)\), set bycoord_input.Integration coordinates are chosen by
integ_coords(Field sources and coordinates). Canonical choices fill afield_can_twith \(A_\vartheta, A_\varphi, h_\vartheta, h_\varphi, B\) and their first and second derivatives, then add \(H, p_\vartheta, v_\parallel\) and their derivatives in \((r,\vartheta,\varphi,p_\varphi)\).field_can_from_namesets the procedure pointersevaluate,integ_to_refandref_to_integ. RK45 usesmagfie, a procedure pointer set byinit_magfie(id), which returns \(B\), \(\sqrt g\), \(\nabla\ln B\), \(h_i\), \(h^i\) and \((\nabla\times\mathbf h)^i\).
Integrators¶
tracer_t (src/simple.f90) holds one marker’s field_can_t,
symplectic_integrator_t, multistage_integrator_t (Gauss and Lobatto
stages) and full-orbit state. orbit_sympl_init maps integmode
(Running SIMPLE) to a step routine raw_timestep_sympl and its
quasi-symplectic fallback; orbit_timestep_sympl wraps it with the LCFS
event search. RK45 (integmode = 0) integrates velo_can with libneo
odeint_allroutines. The GPU paths do not use procedure pointers: they
evaluate the Boozer field locally and reuse the step algebra of
orbit_symplectic_euler1 and orbit_rk_core.
Python binding¶
python/CMakeLists.txt wraps simple_api.f90, params.f90,
orbit_symplectic_base.f90, orbit_frequencies.f90 and
orbit_invariants.f90 with f90wrap into the extension
_simple_backend. pysimple adds array handling and plotting only;
physics stays in Fortran so that simple.x and pysimple agree. A
Fortran program can link the CMake target simple and use simple_api.
Adding a field source¶
Write
type, extends(magnetic_field_t)insrc/field/withevaluatereturning covariant \(A_i\), \(h_i\) and \(B\) in Gaussian units (G, cm) in its native coordinates; convert units on load ascreate_coils_fielddoes.Detect the file in
field_from_file(src/field.F90). If the native coordinates are not the reference coordinates, wrap the field withcreate_splined_fieldas for coils; it then runs with Meiss, Albert or RK45 onREFCOORDS.A field that needs its own canonical coordinates also needs a
field_can_<name>.f90withevaluate,integ_to_refandref_to_integ, a case infield_can_from_nameand an id inmagfie_sub.Add a roundtrip test and an orbit comparison against an existing source on the same equilibrium, and update Coordinate Systems and Magnetic Fields in SIMPLE.
Adding an integrator¶
Add a constant in
orbit_symplectic_baseand a step routine inorbit_symplectic.f90that advancessi%zfrom afield_can_tand returns aSYMPLECTIC_STEP_*status; reuse the Newton and LCFS event helpers.Add the case to
orbit_sympl_initand, if it has stages, sizemultistage_integrator_t.Document the new
integmodevalue inexamples/simple_full.in.Test the convergence order on the analytic field (
integ_coords = -1), the boundedness of \(H\) and \(p_\varphi\) and a short loss comparison against an existing scheme.
IMAS actor¶
python/pysimple/imas (extra imas; usage: IMAS workflow) is
Python on top of pysimple; physics stays in the Fortran library.
Module |
Role |
|---|---|
|
DD 3/4 check, exact-time slice selection, array and scalar
validation, output provenance ( |
|
|
|
equilibrium IDS to COCOS 3 G-EQDSK, then libneo
|
|
|
|
|
A wall IDS is rejected by run and run_entry before any tracing: the
equilibrium chartmap ends at the LCFS, so a wall would never be queried. Wall
tracing for IDS input needs a chartmap beyond rho_lcfs first.
run converts the equilibrium once, then per population calls
pysimple.init with the actor-owned keys and plasma_profiles=table,
cylindrical_to_reference, trace_parallel, reference_to_cylindrical
and fills one distributions.distribution entry. The native side adds to
src/simple_api.f90: simple_set_profile_table (set_profile_table in
src/profiles.f90, linear in \(s\), replaces &profiles until
reset_profiles), simple_cylindrical_to_reference,
simple_reference_to_cylindrical, simple_reference_info (coordinate
semantics: Coordinate Systems and Magnetic Fields in SIMPLE) and simple_wall_supported
(compile-time CGAL flag stl_wall_available).
Keep when changing it:
libneo’s
field_eqshifts the G-EQDSK flux by its axis value and stops the surface scan atpsimaxin the axis-to-edge direction, soequilibrium.pypasses the signedpsimax\(=(\psi_\text{edge}-\psi_\text{axis})\cdot10^8\) G cm2 (Wb/rad to G cm2). An unsigned value ends reversed-flux scans at the first surface (millimetre-sized chartmap). The signed scan needs libneo with itpplasma/libneo#434, pinned at151f58binpyproject.toml.The converter runs in a child process: a Fortran
STOPor module state inefit_to_boozermust not reach the caller.Native exit codes other than 0, 1, 2 and invalid final states raise; they are never written as losses. Weights are never converted between rate and count implicitly.
Every output IDS passes
validate(); the coordinate API keeps its length scale after the temporary chartmap is deleted.
Tests: test/python/test_imas.py (CTest test_imas, labels imas,
slow; needs pysimple[imas] with libneo’s _efit_to_boozer) covers
NetCDF round trips in DD 3.42.2 and 4.1.1, identifier reordering, COCOS signs
against an analytic circular field, chartmap extent for all flux and field
signs, the coordinate round trip, particle and energy totals, rejection of
invalid native states, the DBEntry path with core_profiles, and both wall
forms; test/tests/test_profiles.f90 covers the profile table. A new IDS
mapping gets a reader that validates through _common, selects time with
select_slice and a test with real IMAS-Python IDS of both DD versions.
Invariants to keep¶
Gaussian CGS inside the code; SI and eV only at input and output.
Angles in radians, never wrapped during integration.
The public state is \((s, \vartheta, \varphi, v/v_0, \lambda)\) in reference coordinates; \(s\) stays normalised toroidal flux at every interface, also for chartmaps where the coordinate is \(\rho\).
Fields and canonical maps are built before the parallel region and read only inside it; per-marker state lives in
tracer_torthreadprivatevariables.No procedure pointers or
class()dispatch inside GPU kernels; the CPU path is unchanged when acceleration is off.
Subsystem invariants for tests are listed in Testing. Known design
limitations are tracked in issues
#288 (VMEC inversion),
#290 (sqgBctr),
#291 (radial scaling),
#292 (global magfie)
and #293 (module-level
canonical coordinates).