Skip to content

Surrogates Python API

The full surrogate library lives in linac_gen.surrogates. Five small modules cover training, persistence, registration, comparison, and the per-element surrogate class itself.

Training

The one-shot orchestrator:

train_surrogate_for_element(...) signature

Arg Type Required Notes
element FieldMapElement Yes source whose fitted_matrix(ref) provides ground truth
ref_template reference particle Yes cloned per sample; only w_kin is varied
n_samples int Yes LHS sample count
ref_w_kin_range (lo, hi) MeV Yes scope on incoming kinetic energy
param_ranges dict[str, (lo, hi)] Yes element attributes to sweep
out_dir path Yes where to save weights.pt + metadata.json
hidden_dims tuple of int (128,128,128) MLP hidden layer sizes
activation "silu"/"tanh"/"relu" "silu" smooth = better Jacobians (M7-friendly)
epochs int 200 training epochs
lr float 1e-3 Adam learning rate
batch_size int 256 minibatch size
val_frac float 0.2 hold-out split for val MAPE
seed int 42 LHS + train RNG seed
lattice_hash str "unknown" recorded in metadata
element_key str / None None defaults to element.name
verbose bool False progress prints
n_workers int / None None ≥2 enables multiprocessing.spawn; output bit-identical to serial for fixed seed

Building blocks (use if you need finer control)

Loading and discovery

The runtime registry

The envelope solver consults the registry through two seams: tracking/envelope.py:_full_matrix_at serves the full-element matrix on the pure-linear path of a 0 mA run (this is where the NN engages), and _fitted_matrix_slice_at handles the SC/per-sub- step bundle walks — whose partial slices always delegate to the wrapped RK4. Register to engage, unregister to disengage.

The registry is process-global state. In a long-running process that runs many comparisons, clear between runs to avoid stale surrogates from a previous lattice contaminating the current one.

Comparing envelopes

CompareReport fields

Attribute Type Meaning
s ndarray arc length (mm)
sigma_baseline / sigma_surrogate dict[str, ndarray] keys "x", "y", "phi", "w"
end_of_line dict[str, float] sigma_{k}_base, sigma_{k}_surr, rel_diff_{k}
wall_baseline_s / wall_surrogate_s float seconds
scope_ok bool True if every query was in scope (OOD silently falls back)
surrogate_names list[str] names engaged this run
notes list[str] warnings (e.g. "no surrogate engaged", "registered but never queried")
nn_calls int NN full-element queries made during the surrogate run (0 at current > 0 — the SC walk stays on RK4)
speedup() method wall_baseline_s / wall_surrogate_s
worst_rel_diff() method max of the four end-of-line rel.diffs
summary_text() method multi-line CLI-friendly summary

compare_envelope preserves the global registry state — whatever was registered before the call is restored after, so it's safe to call in a long-running session.

Comparing multi-particle runs

The MP analogue of compare_envelope runs the multi-particle tracker twice — a pure-RK4 baseline (registry temporarily cleared, MP engagement off), then the surrogate-engaged run — a safe delegate unless registry.set_fast_path_enabled(True) is on, in which case the linear-matrix fast path runs — and diffs the two:

CompareMpReport mirrors CompareReport but for MP quantities: s, sigma_baseline / sigma_surrogate (keys "x", "y", "z"), emit_baseline / emit_surrogate (keys "nx", "ny"), transmission_baseline / transmission_surrogate, end_of_line rel-diffs (σ_x/σ_y/σ_z/ε_nx/ε_ny/transmission), wall times, residual_n_steps, plus the same speedup() / worst_rel_diff() / summary_text() helpers. Like the envelope version, compare_mp restores the whole registry + MP-engagement state afterwards, and each surrogate's own residual_n_steps setting is restored too. residual_n_steps is recorded and restored but reserved — no tracking path reads it (the hybrid RK4-residual mode is not implemented).

Drop-in inference

When you've already registered surrogates, normal HELIX code paths (envelope solver, matcher, parameter scans) pick them up automatically at current = 0 — no changes to your scripts needed.

At current > 0 the solver slice-walks field maps for SC kicks and every partial slice delegates to the wrapped RK4 — the run is bit-identical to the unregistered one and makes zero NN queries.

For ad-hoc inference outside the envelope solver, call surr.fitted_matrix(ref) directly:

Out-of-scope handling

A surrogate is queried with an input vector [w_kin, beta, gamma, *param_values]. Each component must lie within the metadata's scope bounds; otherwise:

Programmatic check before calling:

Cross-references

CLI · Continue to Training guide →