Matching CLI¶
For batch matching without writing Python, HELIX ships a command-line
interface: python -m linac_gen.matching.
TL;DR¶
python -m linac_gen.matching examples/matching_demo.dat \
--algorithm least_squares \
--max-iter 200 \
--report
This parses the .dat, runs the matcher on its embedded ADJUST_* /
SET_* cards, prints a report, and writes the matched lattice to
examples/matching_demo.matched.dat.
Flags¶
Input / output¶
| Flag | Default | Meaning |
|---|---|---|
input (positional) |
required | input lattice .dat |
--out PATH |
<input>.matched.dat |
where to write the matched lattice |
--no-write |
off | skip writing the matched .dat (just report) |
--report |
off | print the full variable / constraint report |
Algorithm¶
| Flag | Default | Meaning |
|---|---|---|
--algorithm NAME |
least_squares |
one of least_squares, differential_evolution, dual_annealing, cmaes, sequential_scan, gradient, bayesopt |
--max-iter N |
200 |
least_squares: max function evals; population algorithms: max generations |
--xtol VAL |
1e-8 |
least_squares step tolerance |
--ftol VAL |
1e-8 |
least_squares residual tolerance |
CMA-ES¶
| Flag | Default | Meaning |
|---|---|---|
--cmaes-sigma F |
0.2 |
initial step as fraction of bound-box width |
--cmaes-popsize N |
0 |
population; 0 = library default 4 + ⌊3·ln(N)⌋ |
--cmaes-parallel N |
1 |
evaluate population across N worker processes; 0 = auto-detect. Only worth >1 for expensive per-eval lattices (FieldMap3D). |
--cmaes-search-solver |
auto |
objective the parallel workers score the search with. auto follows --cost-solver faithfully (parallel + mp genuinely evaluates the multiparticle objective); envelope is the fast envelope-guided hybrid — envelope-scored search, mp-scored baseline/polish/final — as an explicit opt-in (this was the silent behavior before the 2026-07 honesty round). No effect with --cmaes-parallel 1. |
--no-refine |
off | skip the least_squares polish after CMA-ES / bayesopt |
Bayesian optimisation (--algorithm bayesopt)¶
| Flag | Default | Meaning |
|---|---|---|
--bo-prior |
off | physics-informed warm start: scout the box with the cheap envelope cost before the expensive objective. Only active with --cost-solver mp. |
--bo-n-init N |
0 |
initial Sobol samples before the GP is first fit; 0 = default min(2N+2, 12). |
bayesopt reuses --max-iter (number of Bayesian iterations) and is the
sample-efficient pick for expensive (MP) + multimodal matches. Example:
python -m linac_gen.matching examples/ml_bayesopt/bo_demo.dat \
--algorithm bayesopt --max-iter 30 \
--energy 2.5 --current 5 --frequency 162.5 --report
# expensive MP match, physics-informed warm start:
python -m linac_gen.matching examples/ml_bayesopt/bo_demo.dat \
--algorithm bayesopt --cost-solver mp --mp-n-particles 200 \
--max-iter 8 --bo-prior --space-charge \
--energy 2.5 --current 5 --frequency 162.5 --report
Cost solver (forward-pass model)¶
| Flag | Default | Meaning |
|---|---|---|
--cost-solver KIND |
envelope |
envelope (fast linear, default) or mp (full PIC; 50–100× slower per eval, physics-accurate) |
--mp-n-particles N |
1000 |
MP cost-solver macroparticle count. Lower = faster but noisier cost. |
--mp-seed N |
42 |
MP cost-solver RNG seed for reproducible per-eval cost. |
The MP cost-solver uses library-default SpaceChargeConfig /
StepConfig; the GUI passes the Numerics tab's config explicitly,
which the CLI doesn't (yet) read from a .lgproj.
Sequential-scan algorithm¶
| Flag | Default | Meaning |
|---|---|---|
--seqscan-passes N |
2 |
full passes through the element list |
--seqscan-steps N |
7 |
bracket steps per parameter per pass |
--seqscan-step-frac F |
0.10 |
per-step displacement = F × (vmax − vmin) |
--seqscan-reversal KIND |
both_grew |
both_grew (flip when both transverse AND longitudinal ε exceed reference) or any_grew (stricter; either) |
--seqscan-threshold KIND |
input |
reversal reference: input (compare to beam input ε; tight) or seed_exit (compare to unmatched-lattice exit ε; natural for ε minimisation with intrinsic growth) |
--seqscan-element NAME |
(all ADJUST'd) | scan only elements with this name (repeatable) |
--seqscan-reject-loss |
off | hard-reject any step whose trial transmission falls below --seqscan-loss-threshold-pct. best_x is NOT updated to a loss-inducing step even if cost dropped; direction flips. Closes the ε-gaming gap. Requires --cost-solver mp. |
--seqscan-loss-threshold-pct F |
100.0 |
Transmission floor for the rejection check. 100.0 rejects any loss; 99.9 tolerates sub-permille. |
See Sequential-scan for the physics and when to use each combination.
Forward-pass / space charge¶
| Flag | Default | Meaning |
|---|---|---|
--space-charge |
off | run the forward pass with space charge |
--current mA |
0.0 |
beam current used when --space-charge is set |
Beam defaults¶
A default BeamConfig seeds the envelope; override its fields with
--species, --energy, --frequency, and the --alpha-x /
--beta-x / --emit-x family (and the -y, -z planes). Run
python -m linac_gen.matching --help for the complete list.
The three global algorithms (differential_evolution, dual_annealing,
cmaes) require finite min/max bounds on every ADJUST card; an
open-ended bound aborts the run with an error naming the offending
variable.
When to pick which algorithm¶
| If… | Use |
|---|---|
| LM-friendly objective, all constraints have non-vanishing gradients | least_squares |
Multimodal landscape with coupling resonances, one-sided / clipped residuals (MIN_EMIT_GROWTH, SET_SIZE_MAX), or sync-phase sign flips |
cmaes + LS polish |
| Highly non-smooth or discontinuous cost | differential_evolution or dual_annealing |
| Lattice is exclusively linear and you want exact Jacobian gradients | gradient |
cmaes is the recommended default for emittance-minimisation
workflows because the one-sided MIN_EMIT_GROWTH residual has zero
gradient on the "drop" side, which defeats scipy's LM. The default
refine=True chains a least_squares polish from the best CMA-ES
point so you still get LM-quality precision on the active constraints.
How it works¶
- Parse the input
.datviaparse_tracewin(). - Collect
ADJUST_*variables andSET_*constraints from the lattice. - Build a
BeamConfigfrom the CLI beam flags. - Run
match()with the chosen--algorithm. - Apply the matched values back into the lattice elements.
- Write the matched lattice to
--out(unless--no-write).
The process exit code is 0 on a converged match, 1 otherwise.
Examples¶
CMA-ES with default LS polish¶
$ python -m linac_gen.matching examples/matching_demo.dat \
--algorithm cmaes --max-iter 80 --report
[match] 8 elements; algorithm=cmaes; cost_solver=envelope; running without SC
[match] baseline cost (at x0) = 1.3240e+00
[match] OK — 880 iters, cost 9.3450e-13, 2.84s
Matching report
═══════════════
status : OK
message : CMA-ES: tolfun + tolx (parallel x1) + LS refine
iterations : 880
cost : 9.345004e-13
elapsed : 2.84 s
Variables:
QUAD_001.gradient -5 → -5.47142 [-30, 30]
Constraints:
SET_SIZE residual rms = 1.3671e-06
Sequential-scan with seed-exit threshold¶
python -m linac_gen.matching examples/emittance_min/pipii_hwr_cm_match_4d_relaxed.dat \
--algorithm sequential_scan \
--seqscan-passes 2 --seqscan-steps 7 --seqscan-step-frac 0.10 \
--seqscan-reversal both_grew \
--seqscan-threshold seed_exit \
--report
MP-mode cost solver (physics-accurate final polish)¶
python -m linac_gen.matching examples/emittance_min/pipii_hwr_cm_match_4d_relaxed.dat \
--algorithm cmaes --max-iter 40 --cmaes-popsize 11 \
--cost-solver mp --mp-n-particles 1000 \
--space-charge --current 10.0
Expect 1–2 orders of magnitude longer wall time than the envelope path because each evaluation runs a full multi-particle simulation.
Cross-references¶
- Python API — the
match()function. - SET / ADJUST —
.datcard syntax. - Recipes — worked examples.