Skip to content

Convergence guide

Practical guidance on grid sizing, particle count, and step density for HELIX simulations to actually converge. Wrong choices produce plausible-looking but wrong answers — this is where most simulation hours go.

TL;DR — production defaults

For a typical PIP-II-stage simulation (post-RFQ, ≤ 5 mA, ≤ 1 km lattice, 1 % σ accuracy target):

Parameter Value Notes
Particles n_particles 5000-20000 1 % σ floor at √N statistical limit
Grid nx=ny=nz 96 (the default; 64 for quick tests) 96³ converged for 5 mA
grid_extent 5.0σ ~99.99% Gaussian retention
kernel cic (with RF) or tsc (no RF) see Kernels
green_kind igf always
Step density PARTRAN_STEP 100 50 step1 = 100/m integration substeps, step2 = 50/m SC kicks (both apply to DRIFT + FIELD_MAP only)

PARTRAN_STEP semantics — the card carries two steps-per-metre numbers:

  • step1 drives the integration substep count for DRIFT and FIELD_MAP elements;
  • step2 sets the space-charge kick cadence inside those same elements;
  • quads, bends, solenoids (and other transfer-map elements) are always tracked in exactly 2 substeps with one mid-plane SC kick, regardless of this config;
  • drift_single_push (StepConfig, default on): a drift's map is exact, so its step1 sub-steps only matter for what happens between them — space-charge kicks, sub-step diagnostics, the bunch-train phase fold. When none of those is active the tracker applies the map once and locates any loss inside the pipe analytically (see Drift). Where space charge kicks, the sub-steps stay and results are unchanged; a drift inside a fully compensated region (SpaceChargeComp factor ≥ 1) applies no kick and is pushed once too. A zero-current run keeps the same transfer maps and no longer spends most of its time in drift sub-steps; the one place its result differs from the sub-stepped walk is a particle already outside a drift's pipe at the entrance (a drift narrower than the element before it), which is now lost at the entrance where the sub-stepped walk kept it if it had wandered back inside by the first sub-step end — transmission can be slightly lower there, and is closer to TraceWin's every-step check. Switch it off (Numerics tab, --drift-single-push off, or StepConfig(drift_single_push=False)) to reproduce the sub-stepped walk of HELIX ≤ 1.9.1 exactly. Note that with it on, a step1 convergence scan at zero current only probes field maps.

Convergence checks

For any production run, verify convergence in three dimensions:

1. Particle count

Run the same lattice with n_particles ∈ {2000, 5000, 10000, 20000} and plot σ_x at end of lattice. The 1/√N noise floor visible at 2000 should disappear at 10000. If σ_x changes significantly beyond 10000, the simulation hasn't converged — likely indicates strong non-Gaussian effects.

2. Grid size

Run with nx ∈ {32, 48, 64, 96, 128} (square cubic grids), plot σ_x at end of lattice. Should plateau by 96; if not, double the extent or check for unusual aspect ratios.

The scans live in the GUI's Numerics tab (Scan parameters group) — see Numerics tab; it automates the grid, extent, step1, and step2 sweeps.

3. Step density

Run with step1_per_m ∈ {25, 50, 100, 200}. Below 50/m, short-element field maps under-sample. Above 100/m, gains diminish but cost rises linearly.

Memory note: BTL CIC ε_z growth

Per memory note project_cic_grid_noise_in_no_rf_transport: in long no-RF transport sections like the BTL, the CIC kernel + IGF Green's function suffer from grid-noise accumulation that blows up ε_z by ×14. Switching to TSC drops it to ×1.6 (matches TraceWin's ×1.7). See Kernels.

Memory note: PXIE LEBT silent failure

Per feedback_lebt_scc_and_envelope_continuous: PXIE LEBT needs Ki=1 (SC compensation) AND the envelope solver needs continuous=True. Forgetting either produces 6.93% mean error that looks like a physics bug. Always verify these flags for LEBT-class runs.

Memory note: PIC kernel calibration offset

Per project_lg_pic_kernel_calibration_offset: HELIX's PIC kernel has a documented ~3% σ overshoot vs TraceWin partran on PIP-II. This is separate from grid-noise / convergence issues — it's a known absolute-calibration bias. When validating against TraceWin, expect ~3% residual on σ. Higher residuals usually indicate a parameter mismatch or convergence issue.

Runtime expectations — the transfer-matrix cache and who uses it

HELIX has an opt-in per-element transfer-matrix cache keyed on (id(elem), elem_fingerprint, ref_fingerprint) with 12-decimal FP rounding (linac_gen/tracking/matrix_tracking.py). Opt-in means a caller must pass a cache= dict explicitly; with the default cache=None every matrix is recomputed on every call.

Who actually passes a cache: only the GUI's structure-analysis panels — Phase Advance and Tune Depression — via structure_phase_advance(..., cache=...). There, the first analysis of a freshly-loaded full PIP-II lattice costs ~340 s and repeat analyses ~3 s (127× faster).

Who does NOT: envelope and multi-particle runs (the solvers call get_element_matrix(element, ref) uncached), and the matching engine — matching mutates element parameters in place on every optimiser step, which is exactly the situation the cache is documented as unsafe for. A simulation run therefore neither benefits from nor populates the cache; expect the same wall-clock on every run.

Don't try to pre-warm the cache in a background thread

A MatrixCacheWarmer lives at gui/linac_gen_gui/interphase/services/matrix_cache_warmer.py but is deliberately not instantiated by the GUI (see comment at app.py:174-177). Past experiments showed that even with a running_changed guard the warmer's fitted-matrix Jacobian walks competed for CPU on project / lattice load and made the GUI feel slow. If you re-enable it without a new design (e.g. a truly idle warmer that fires only after N seconds of zero UI events), expect the same regression.

Cross-references

  • Models — picking the right SC model first.
  • Kernels — CIC vs TSC.
  • PIP-II validation — full 256m benchmark with documented residuals.

DC mode · Continue to Differentiable PIC →