First Steps¶
A hands-on introduction to scpn-control. No plasma physics background required.
Prerequisites¶
- Python 3.10+
- numpy (installed automatically as a dependency)
What this tutorial proves¶
This page is not only a code recipe. It is the smallest reproducible workflow that turns three concerns into one observable chain:
- Physics assumptions are declared through a machine preset and equilibrium input.
- Control intent is expressed through a controller object and an explicit step interface.
- Evidence discipline is preserved when the same output is reused in validation and benchmark reports.
The value is in closure: one short run is easy to re-run, one command sequence is easy to review, and one report chain is easy to trace.
Installation¶
For optional extras:
pip install scpn-control[jax] # JAX autodiff solvers
pip install scpn-control[neuro] # optional SC-NeuroCore bridge
pip install scpn-control[rl] # stable-baselines3 PPO agent
Your First Tokamak Configuration¶
scpn-control ships with frozen dataclass presets for ITER, SPARC, DIII-D, and JET. Each encodes published machine parameters (major radius, field, current, shaping).
from scpn_control.core.tokamak_config import TokamakConfig
iter_cfg = TokamakConfig.iter()
sparc_cfg = TokamakConfig.sparc()
print(f"ITER: R0={iter_cfg.R0} m, B0={iter_cfg.B0} T, Ip={iter_cfg.Ip} MA")
print(f"SPARC: R0={sparc_cfg.R0} m, B0={sparc_cfg.B0} T, Ip={sparc_cfg.Ip} MA")
print(f"ITER aspect ratio: {iter_cfg.aspect_ratio:.2f}")
Output:
Your First Equilibrium¶
The FusionKernel solves the Grad-Shafranov equation on a 2D (R, Z) grid via
Picard iteration. It reads a JSON configuration specifying the reactor geometry,
coil positions, and solver parameters.
from scpn_control.core.fusion_kernel import FusionKernel
kernel = FusionKernel("iter_config.json")
result = kernel.solve_equilibrium()
print(f"Converged: {result['converged']}")
print(f"Iterations: {result['iterations']}")
print(f"Residual: {result['residual']:.2e}")
print(f"Psi grid shape: {result['psi'].shape}")
The iter_config.json file ships in the repo root. It defines a 129x129 grid
spanning R=[2, 10] m and Z=[-6, 6] m with seven PF/CS coils.
What the output means:
psi-- the poloidal flux on the (NZ, NR) grid. Contours of constant psi are flux surfaces. The innermost contour encloses the magnetic axis; the outermost closed contour is the Last Closed Flux Surface (LCFS).converged-- whether the Picard residual dropped below the threshold (default 1e-4).- After solving,
kernel.Psiholds the flux grid andkernel.J_phiholds the toroidal current density.
Your First Controller¶
The H-infinity controller provides guaranteed robust stability for vertical
position control. get_radial_robust_controller builds a 2-state vertical
stability plant and synthesises Riccati-based gains.
from scpn_control.control.h_infinity_controller import (
HInfinityController,
get_radial_robust_controller,
)
ctrl = get_radial_robust_controller(gamma_growth=100.0, damping=10.0)
print(f"gamma = {ctrl.gamma:.2f}")
print(f"Robust feasible: {ctrl.robust_feasible}")
print(f"State-feedback gain F: {ctrl.F}")
# Step the controller in a loop
dt = 1e-3
for _ in range(500):
u = ctrl.step(error=0.1, dt=dt)
The gamma_growth parameter is the vertical instability growth rate in 1/s.
ITER-like values are ~100; SPARC is ~1000. The controller solves two continuous
Algebraic Riccati Equations, then discretises via zero-order hold at the
requested dt.
Your First Transport Run¶
TransportSolver extends FusionKernel with 1.5D radial transport. It evolves
temperature and density profiles using Crank-Nicolson implicit diffusion, coupled
to the 2D equilibrium.
from scpn_control.core.integrated_transport_solver import TransportSolver
solver = TransportSolver("iter_config.json", nr=50)
# Evolve 10 time steps of 0.1 s each with 50 MW auxiliary heating
for step in range(10):
dt, P_aux = 0.1, 50.0
tau_E, Q = solver.evolve_profiles(dt, P_aux)
if step % 5 == 0:
print(f"Step {step}: tau_E={tau_E:.3f} s, Q={Q:.2f}")
print(f"Central Ti: {solver.Ti[0]:.2f} keV")
print(f"Central ne: {solver.ne[0]:.2f} x10^19 m^-3")
evolve_profiles returns (tau_E, Q) -- the energy confinement time and the
fusion gain factor. The solver updates solver.Te, solver.Ti, and solver.ne
in place.
For multi-ion D-T transport with helium ash:
How the pieces connect¶
The sections above are intentionally ordered from data model to actuator intent:
- define a machine geometry,
- compute equilibrium structure,
- choose a controller family,
- attach a transport evolution,
- convert symbolic control logic into runnable execution.
Each step is independently replaceable, but the evidence chain is cumulative.
Your First SPN¶
A Stochastic Petri Net defines the control logic as a bipartite graph of places
(state variables) and transitions (firing rules). The FusionCompiler compiles
this graph into a spiking neural network.
from scpn_control import StochasticPetriNet, FusionCompiler
net = StochasticPetriNet()
net.add_place("plasma_state", initial_tokens=1.0)
net.add_place("control_active", initial_tokens=0.0)
net.add_transition("activate", threshold=0.5)
# Arc from place to transition (input) and transition to place (output)
net.add_arc("plasma_state", "activate", weight=0.8)
net.add_arc("activate", "control_active", weight=0.9)
# Compile to SNN
compiler = FusionCompiler()
compiled = compiler.compile(net)
print(f"Places: {net.n_places}")
print(f"Transitions: {net.n_transitions}")
print(f"W_in shape: {compiled.W_in.shape}")
print(f"W_out shape: {compiled.W_out.shape}")
Each place maps to a LIF neuron membrane potential. Each transition maps to a
synaptic connection with the arc weight. The CompiledNet stores sparse matrices
W_in (transitions x places) and W_out (places x transitions).
To run closed-loop control with the compiled net, wrap it in a
NeuroSymbolicController with observation-to-place mappings and readout
configuration. See examples/tutorial_01_closed_loop_control.py for the
full pipeline.
CLI Quick Reference¶
# Closed-loop control demo (PID, SNN, or combined)
scpn-control demo --scenario combined --steps 500
# Timing benchmark: PID vs SNN step latency
scpn-control benchmark --n-bench 5000 --json-out
# Validate solver against reference data
scpn-control validate
# WebSocket phase-dynamics server
scpn-control live --port 8765 --zeta 0.5
# Hardware-in-the-loop test against recorded disruption shots
scpn-control hil-test --shots-dir validation/reference_data/diiid/disruption_shots
Next Steps¶
- tutorials.md -- five self-contained tutorial scripts covering the full stack (GS equilibrium, JAX autodiff, PPO RL, neural transport, adaptive phase dynamics).
- notebooks.md -- interactive Jupyter notebooks (Q10 breakeven, SNN compiler walkthrough, H-infinity demo, phase dynamics).
- theory.md -- mathematical foundations: SPN formalism, Kuramoto-Sakaguchi model, UPDE, Lyapunov stability analysis.
- glossary.md -- definitions of all plasma physics and control theory terms used in the codebase.
Evidence quick path¶
After the introductory examples, generate one small evidence artefact so the validation workflow is concrete:
PYTHONPATH=src python scripts/benchmark_native_handoff.py \
--steps 500 \
--tick-interval-s 0 \
--transport-backend std \
--json-out validation/reports/native_handoff_smoke.json \
--markdown-out validation/reports/native_handoff_smoke.md
Use this only as local smoke evidence unless the run records isolated benchmark
context. Claim-bearing releases should cite the persisted validation reports
listed in Benchmarks and admitted by scpn-control validate.
Completion criteria after first steps¶
After the first-run sequence, keep the result in a recoverable artifact and move to the evidence step.
- Save the exact command sequence with environment details.
- Copy or archive outputs into
validation/reports/if they are used in review. - Keep one unresolved note if a claim still depends on external reference validation.
This keeps the first-run section useful as onboarding and as traceable evidence history.
Practical use and scope¶
Use this as the first executable sequence after environment setup.
- Follow the steps in order to establish a baseline local run that is measurable and repeatable.
- Confirm prerequisite dependencies before launching any control-loop benchmark.
- Keep this as the baseline before moving into pulsed or deployment-oriented tutorials.