First Steps¶
A hands-on introduction to scpn-control. No plasma physics background required.
Prerequisites¶
- Python 3.10+
- numpy (installed automatically as a dependency)
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:
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.