Running an error study¶
Three paths to launch an error study: Python script, GUI Error Study tab,
or a .dat with embedded ERROR_* directives.
Path 1: Python (programmatic)¶
from linac_gen.io.tracewin_parser import parse_tracewin
from linac_gen.core.config import BeamConfig
from linac_gen.errors.error_model import ErrorStudy
lattice, _ = parse_tracewin("examples/error_studies/quad_alignment_tolerance/quad_alignment.dat")
beam_cfg = BeamConfig(
species="proton", energy=2.5, frequency=325.0,
current=5.0, n_particles=5000,
distribution="gaussian", cutoff=4.0,
emit_nx=0.25, emit_ny=0.25, emit_z=0.30,
alpha_x=0.0, beta_x=0.5,
alpha_y=0.0, beta_y=0.5,
alpha_z=0.0, beta_z=1.0,
)
study = ErrorStudy(lattice, beam_cfg, n_seeds=100)
# (errors auto-loaded from the .dat's ERROR_QUAD_NCPL_STAT)
results = study.run()
stats = results.transmission_stats()
print(f"Mean transmission: {stats['mean']:.2f} %")
print(f" range over seeds: {stats['min']:.2f} - {stats['max']:.2f}")
sx_mean = results.mean("sigma_x")
sx_std = results.std("sigma_x")
print(f"σ_x at end (mean ± 1σ): {sx_mean[-1]:.3f} ± {sx_std[-1]:.3f}")
ErrorStudy.run():
- Draws a per-seed sample of every error (alignment, field, beam input).
- Applies the perturbation to a per-seed lattice/beam copy.
- Runs the seed's forward model: by default it tracks the
multi-particle beam through the perturbed lattice (pass
sc_configto add PIC space charge); withmode="envelope"it runs the rms envelope (env_solver"matrix" or "sacherer", space charge from the beam current, no particle losses; the Sacherer envelope takes a solenoid map'svoltage_reldraw as every mode does, not a hard-edge quadrupole'sgradient_rel, and refuses a map it has no model of: a seed whose draws push a steering corrector past its bounds (the LEBT example's first two corrector pairs past 2.4 to 2.5 mrad, the other corrector maps past 20 mrad) stops the study, see DC mode) and uses the registered surrogates when the design lattice with them agrees with the native run (see A study and its surrogates). - Keeps the seed's recorder (a
DiagnosticRecorder, or theEnvelopeResultsof an envelope study; whole seeds only). - Returns an
ErrorStudyResultsthat aggregates across seeds on demand (mean/std/percentile/transmission_stats).
ErrorStudy constructor¶
ErrorStudy(
lattice: Lattice,
beam_config: BeamConfig,
n_seeds: int = 100,
sc_config: SpaceChargeConfig | None = None, # None = no space charge
base_seed: int = 0, # RNG offset, for reproducibility /
# extending a study with new seeds
*,
mode: str = "mp", # "mp" tracks particles; "envelope"
# runs the rms envelope
env_solver: str = "matrix", # envelope study: "matrix" or "sacherer"
)
run() runtime contract¶
should_stop— optional zero-argument callable, polled at the top of each seed loop and re-checked after tracking. A stop request mid-seed discards that seed entirely — a truncated recorder never enters the ensemble (its arrays would be shorter than the others', corrupting every mean/std/percentile).progress_cb— optional callable invoked asprogress_cb(i_done, n_requested)after each completed seed.- On early stop the returned results carry only whole seeds:
results.n_seedsis the completed count andresults.n_requestedthe asked-for count, so callers can report "k of n". base_seed(constructor) offsets every per-seed RNG draw — two studies with the same configuration but differentbase_seedvalues produce statistically independent ensembles.n_workersis accepted for forward compatibility but currently ignored — seeds always run serially (a value > 1 emits a warning; for parallel ensembles run several studies with differentbase_seedvialinac_gen.parallel.scan_pool).
Draw conventions (changed 2026-07)
Gaussian errors are truncated by redrawing until the value falls inside ±cutoff·σ (TraceWin convention) — the old clip-based truncation piled tail mass exactly on the limits. RNG streams are now purpose-separated per seed (element / beam / BPM-noise), and BPM noise varies per seed instead of repeating one sequence across the whole ensemble. Together with the FieldMap-amplitude fix this means error-study results are not bit-reproducible against pre-2026-07 runs — the new statistics are the correct ones.
Adding errors programmatically¶
study.add_error(
pattern="QUAD_*",
parameter="dx",
distribution="gaussian",
sigma=0.2,
cutoff=3.0,
)
study.add_beam_error(
parameter="centroid_x",
distribution="gaussian",
sigma=0.05,
)
These add to the auto-loaded list — you can mix .dat ERROR_*
directives and Python add_error() calls.
Path 2: GUI Error Study tab¶
- Open the lattice (File → Open Lattice or
Ctrl+O). - Open the Error Study tab (between Surrogates and Failure Study).
- Either:
- If the
.dathasERROR_*directives, they're already loaded; the form shows them in the "Registered errors" list. - Or use the form to add element / beam errors interactively.
- Set
n_seeds(default 50; 100-200 for production) and the forward model (multiparticle, the default, or envelope). - Click Run study. Status bar shows progress; results land
on
state.error_study_results. - Open Results tab → click the Error study ensemble tile.
For details see GUI → Error Study tab.
Path 3: .dat with ERROR_* directives¶
The minimum-effort path: write a TraceWin-compatible .dat,
embed ERROR_* cards, and HELIX figures out the rest. Five
example projects under examples/error_studies/ show the
convention:
examples/error_studies/
├── quad_alignment_tolerance/
├── quad_field_errors/
├── cavity_rf_jitter/
├── beam_input_jitter/
└── combined_realistic/
Each is a self-contained .lgproj you can open in the GUI and
run with one click.
Performance tips¶
All rows are multi-particle runs (an envelope study, mode="envelope",
costs one envelope run per seed); "no SC" means sc_config=None, "MP+SC"
means a PIC sc_config was passed. Times are indicative and scale with
n_particles.
| Lattice | Seeds | Mode | Wall time |
|---|---|---|---|
| Small (~10 elements, 5000 particles) | 50 | MP, no SC | ~5 s |
| Small | 50 | MP+SC | ~30 s |
| PIP-II MEBT | 100 | MP, no SC | ~1 min |
| PIP-II MEBT | 100 | MP+SC | ~30 min |
| Full PIP-II 256m | 100 | MP, no SC | ~10 min |
| Full PIP-II 256m | 100 | MP+SC | ~12 hours (don't!) |
For full-machine MP+SC studies, parallelise across machines or run overnight. Most tolerance work runs without space charge for speed — final verification only with the PIC solver enabled.