.dat file reference¶
HELIX reads TraceWin .dat files natively. This page is the
keyword reference: every directive HELIX recognises, with parameter
slots and behaviour notes.
HELIX .dat files use millimetres — not directly portable to real TraceWin
All lengths in a HELIX .dat are interpreted and written in
mm, matching the internal HELIX unit convention
(linac_gen/io/tracewin_parser.py, linac_gen/io/tracewin_writer.py).
A file written by HELIX round-trips cleanly through HELIX's own
parser, but no unit-for-unit parity with the real TraceWin
program is claimed — do not feed HELIX-written .dat files to
TraceWin (or vice versa) without checking the units yourself.
File anatomy¶
; Comments start with semicolon
TITLE Example PIP-II MEBT
FREQ 162.5
PARTRAN_STEP 100 50
; Element cards (each on its own line)
DRIFT 100 20
QUAD 100 8.5 20
DRIFT 100 20
; Subsection markers
LATTICE
DRIFT 200 20
LATTICE_END
; Error directives (consumed by error_study)
ERROR_GAUSSIAN_CUT_OFF 3
ERROR_QUAD_NCPL_STAT 6 2 0.2 0.2 0 0 0 0.5 0 0 0 0
; HELIX-specific extensions (comment-prefixed)
;@LG dc_kernel=gaussian
;@LG nx=64 ny=64 nz=64
END
Header / control directives¶
| Keyword | Slots | Meaning |
|---|---|---|
TITLE |
string | lattice title (informational) |
FREQ |
f (MHz) | sets reference frequency; updates with each card |
PARTRAN_STEP |
step1 step2 (per metre) | integration step density → lattice.step_config |
LATTICE |
(none) | start of lattice subsection |
LATTICE_END |
(none) | end of lattice subsection |
END |
(none) | end of file |
FIELD_MAP_PATH |
path | directory for FIELD_MAP files |
REPEAT_ELE is not implemented: the parser has no branch for it,
so it lands on the unsupported-card path (a warning in
metadata["warnings"], or ValueError with strict=True). Expand
repeats manually before importing.
Element cards¶
| Keyword | Parameter slots | HELIX class |
|---|---|---|
DRIFT |
L aperture [aperture_y x_shift y_shift] | Drift |
QUAD |
L G aperture [skew g3 g4 g5 g6 gfr] | Quadrupole |
SOLENOID |
L B aperture | Solenoid |
BEND |
angle ρ [field_index aperture hv] | Dipole |
EDGE |
pole_rotation ρ gap k1 k2 [aperture hv] | Edge |
GAP |
e0tl φ_s aperture [p_flag] | RFGap |
FIELD_MAP |
geom L θ_i R kb ke Ki Ka FileName [p_flag] | FieldMap |
RFQ_CELL |
(Toutatis format) | RfqCell |
THIN_STEERING / STEERER |
bx_l by_l aperture [elec] | Steerer |
APERTURE |
dx dy aperture_type | Aperture |
MARKER |
name | Marker |
Labels. A card may carry a TraceWin label (Q01: QUAD …, D01T : THIN_STEERING …,
D01BPM:DIAG_POSITION …). The parser keeps the raw label on every element the
line creates as element.label (duplicates allowed — fnalscl labels both halves
of its first trim D01T) while element.name stays the generated unique
identifier (QUAD_001, STEER_001; BPM markers are named after their label,
de-duplicated). The writer emits the label again on single-line cards, so a
saved deck keeps the device names the control system uses; unlabeled decks are
written without labels.
A label may contain spaces, as in the TraceWin manual's own example
SOL 1 : SOLENOID 410 0.25 100 (label SOL 1). A line is read this way
only when its first word is not a card keyword, so TITLE Linac: v2
stays a title. A label with nothing after it — HWR CM:,
Treaty Point:, WPM : — marks a location: it adds no element (so
element counts and index-based ADJUST / --set targets are
unaffected), raises no warning, does not close an open SUPERPOSE_MAP
cluster, and is listed in the parser's metadata["label_lines"] as
[line, name]. The built-in diagnostic names (BPM :, XCOR :,
ACCT : …) still create their markers, and a name that looks like a
card keyword (upper case with an underscore, e.g. PLOT_DST :) is
still reported as an unsupported card. The
writer does not re-emit labels that contain spaces or colons.
Error directives¶
See Errors → ERROR_* directives for full coverage. Quick map:
| Keyword | Effect |
|---|---|
ERROR_GAUSSIAN_CUT_OFF σ_max |
sets default truncation |
ERROR_SET_RATIO r1 r2 … |
multi-scale sweep ratios |
ERROR_QUAD_NCPL_STAT N r dx dy φx φy φz dG dG3 dG4 dG5 dG6 [Nb] |
quad alignment + field |
ERROR_CAV_NCPL_STAT N r dx dy φx φy E φ dz [Nb] |
cavity alignment + voltage + phase |
ERROR_BEND_NCPL_STAT N r dx dy φx φy φz dg dz |
dipole alignment + field |
ERROR_BEAM_STAT r dx dy dφ dxp dyp de dEx dEy dEz mx my mz dIb |
beam-input jitter |
r = distribution code: 0 → gaussian (no constant special-case; the
global cutoff applies as usual); 1 → uniform; 2 → gaussian; 4 / 5
(binary) → treated as gaussian (coarse approximation).
Matching directives¶
| Keyword (with slots) | Effect |
|---|---|
SET_SYNC_PHASE |
(no args) interpret the next FIELD_MAP's θᵢ as the synchronous phase |
SET_TWISS family αx βx αy βy αz βz kax kbx kay kby kaz kbz |
Twiss target (no emittance slots; the k-flags select which values are constrained) |
SET_POSITION k x xp y yp |
centroid-position constraint |
SET_SIZE k x y φ k2 |
beam-size constraint |
SET_ACHROMAT k f1 f2 plane |
dispersion = 0 |
ADJUST target param_idx [link_group vmin vmax start_step kn] |
generic matching variable — target is a family/section, param_idx the parameter index |
ADJUST_STEERER N vmax first_step |
steerer variable (both planes) for orbit correction |
ADJUST_STEERER_BX / ADJUST_STEERER_BY |
single-knob variants: _BX = bx_l knob → vertical plane; _BY = by_l → horizontal |
ADJUST_BEAM_TWISS / _CENTROID / _EMIT / _CURRENT |
beam-input matching variables (diag_n + flag tail) |
There are no per-element ADJUST_QUAD / ADJUST_SOLENOID /
ADJUST_DIPOLE / ADJUST_DRIFT / ADJUST_GAP keywords — element
parameters are adjusted through the generic ADJUST card.
HELIX-specific extensions¶
Comment-prefixed (won't break TraceWin):
A ; HELIX_FOIL directive declares a scattering / stripper
Foil element — also comment-prefixed, so
TraceWin ignores it:
A ; HELIX_SC_GRID directive changes the 3-D bunched PIC solver's
grid extent (in beam sigmas) from its position onward — also
comment-prefixed. Use it when one deck spans sections with different
grid needs, e.g. a linac at the project default followed by a long
no-RF transfer line whose dispersive orbits and halo need a much wider
grid (the standalone PIP-II BTL project uses 20 σ):
A ; HELIX_SET_ENERGY directive sets the reference kinetic energy at its
position, and ; HELIX_SHIFT_ENERGY adds to it, in every linac mode — also
comment-prefixed. TraceWin's SET_BEAM_ENERGY and SET_BEAM_E0_P0 are
matching criteria (the beam's energy, and its energy and phase against the
linac's) and change nothing in tracking; HELIX set and shifted the energy
with them until 2026-11, and a deck that carries them gets one notice:
; HELIX_SET_ENERGY <MeV> [name=<name>] [label=<label>]
; HELIX_SHIFT_ENERGY <MeV> [name=<name>] [label=<label>]
A set energy must be above 0. A line that names the card but cannot be read (a unit after the number, a missing or non-finite value) is reported and skipped, and a strict parse refuses it.
It applies in both fixed and adaptive grid modes (fixed mode re-derives its frozen grid once, from the beam at the card's position). The card affects only the bunched 3-D PIC solver: DC/continuous kick models and envelope runs ignore it (the tracker warns once if it can have no effect). Several cards may appear; each takes effect from its own position.
See HELIX extensions.
Importing MAD-X lattices¶
Besides TraceWin .dat, HELIX reads a subset of MAD-X lattice
files (.madx / .seq). In the GUI, Open Lattice… accepts them
directly; programmatically:
from pathlib import Path
from linac_gen.io.madx_parser import parse_madx
# A minimal MAD-X file to demonstrate (or point at your own):
Path("ring.madx").write_text("""\
lq := 0.1;
qf: quadrupole, l=lq, k1=2.5;
qd: quadrupole, l=lq, k1=-2.5;
cell: sequence, l=1.0;
qf, at=0.25;
qd, at=0.75;
endsequence;
beam, particle=proton, energy=0.941;
use, sequence=cell;
""")
lattice, meta = parse_madx("ring.madx") # → (Lattice, metadata)
Path("ring.madx").unlink()
meta carries "title", "warnings" (a list — unsupported
constructs are skipped, not fatal), "reference" (the
ReferenceParticle built from the BEAM command, the magnets' rigidity;
a 1 GeV kinetic-energy beam when the deck gives no energy) and
"reference_declared" (true only when the BEAM gives the energy:
energy, pc, gamma, beta or brho).
The importer reads a deck the way MAD-X 5.09 does; each rule below is
checked against MAD-X itself in tests/io/test_madx_lines.py.
Supported:
!/////* */comments (whichever comes first on a line wins).- Expressions with
+ - * / ^(a^b^cis(a^b)^c, as in MAD-X), MAD-X's constants (pi,twopi,e,clight,pmass,emass,degrad,version…) and functions (sqrt,abs,sin…tanh,asin,acos,atan,log,log10,exp,erf,erfc,floor,ceil,round,frac,sinc), dotted names (kqf.a12),elem->attr(written without blanks, as MAD-X requires) andbeam->brho-style reads. A name used where it is not (yet) defined is 0, as in MAD-X, and the names are listed once in the warnings; a division by zero is 0 (MAD-X's rule), RANF/GAUSS/TGAUSS are taken at their mean, andtable(…)reads 0 — as MAD-X does before anyTWISS,SURVEY,CREATE… made a table, and always where it is written in anIForWHILEcondition; elsewhere after one (a variable set to it, say) the importer, which keeps no tables, cannot know the value — all reported. name = expr(evaluated at once),name := expr(evaluated where it is used),CONST/REAL/INTdeclarations (anINTis truncated towards zero and held in 32 bits); redefining a built-in orCONSTconstant is ignored, as MAD-X does.- Element definitions,
child: parent, …inheritance (the parent is copied when the child is defined), deferred attributesattr := expr,elem->attr = …andelem, attr = …modifications. SEQUENCE … ENDSEQUENCE(at,at := …,refer;from = namecounts from the centre of the firstname,from = name[k]from the k-th,from = seq$start/seq$endfrom the ends; members are kept in the order written and no drift is laid for a gap under 1 µm, as MAD-X does). Assignments,EXEC,IFandWHILEinside a sequence run as they do anywhere. Beam linesLINE = (…)withN*, reflection and nesting;USE, SEQUENCE=…/PERIOD=…. Member positions, the sequence length and element lengths are those at the lastUSE(MAD-X expands the line there: a laterpq = …,q->l = …, bend angle orOPTION, RBARCmoves nothing; aUSEof a sequence not yet defined freezes nothing, as MAD-X skips it, though when no otherUSEnames a line it still names the one imported); strengths and angles are those at the end of the deck.BEAM(a laterBEAMchanges only what it gives;particle=ionhas MAD-X's NMASS),OPTION, RBARC.IF/ELSEIF/ELSEandWHILEwith MAD-X's conditions (a blank between operator characters is dropped, so< =is<=;a bstays two names): one comparison (==,<>,<,>,<=,>=), or two joined by&&or||. The first part decidesfalse && …andtrue || …; a first part that is not one comparison ((a == 1),a) counts as not false. Otherwise what follows must be one comparison. Anything else — three comparisons the first does not decide, a lone(a == 0),if (a),1 < a < 3— is what MAD-X calls an illegal IF construct: it is false, with a warning. A statement that no;ends (before a block's closing brace, or at the end of the file) does not run, a block never closed does not run, and a}that closes no block ends reading (unless its statement holds as many{as}, as intitle, "a}b{c").MACROwithEXEC(also writtenexec namewithout the comma): each parameter is replaced as text wherever it appears, inside names too (lhcBIMwithBIM = b1islhcb1), and an argument is one MAD-X token —mk(1+1)passes1,mk(qd->k1)passesqd->k1,$ithe value ofias an integer; an argument list that starts with a sign starts with0(mk(-0.5)passes0), a sign elsewhere passes itself.RETURN(the statement under way — anIFbody, every pass of aWHILE, a macro — finishes; nothing after it is read) andSTOP.- A
WHILEloop whose pass runsTWISS,MATCH… or readstable(…)in its body after a table was made is undone (MAD-X would iterate on results the importer does not compute), as is a loop still running after 100 000 passes; both are reported, and the values are those before the loop. HARMON: f = h·βc/C with C the length of the expanded line and h as written (it overridesFREQ, as in MAD-X). AnRFCAVITYwith noVOLT,FREQorHARMONdoes nothing; one with a voltage but no frequency is refused by envelope and matrix runs.- A bend's
K0andK2are kept on theDipole(k0,k2) for ring mode; the linac maps ignore them. A bend's skew componentsK1SandK2Sare not modelled (reported; MAD-X'sTWISSleaves them out too). A bendtiltwritten rounded (1.5708,3.1416) is taken as ±π/2 or π, with a note.
Element mapping:
| MAD-X | HELIX |
|---|---|
DRIFT |
Drift |
QUADRUPOLE |
Quadrupole (k1 → gradient via Bρ; tilt → skew_angle, a bare tilt = π/4; a skew component k1s turns it as MAD-X does: √(k1² + k1s²) at tilt − atan2(k1s, k1)/2) |
SBEND |
Edge + Dipole + Edge (ρ > 0 with the direction in the angle's sign; k1 → field index n = −k1·ρ²; tilt=+π/2 / −π/2 → vertical bend towards +y / −y; pole faces e1/e2 → β = sign(angle)·e, because MAD-X's edge focusing is h·tan e with the signed curvature while HELIX's is tan β/ρ; hgap/fint/fintx → edge gap and K1, with MAD-X's default fint = 0; a negative fintx, MAD-X's default −1, is fint) |
RBEND |
Edge + Dipole + Edge (edges pick up ±angle/2; same extras as SBEND). l is the straight chord under MAD-X's default OPTION, RBARC=true and the arc is l·(θ/2)/sin(θ/2); OPTION, RBARC=false (LHC-style files) makes l the arc length |
KICKER / HKICKER / VKICKER / TKICKER |
Steerer (kick angles → ∫B·dl through the charge-signed Bρ, so an H⁻ deck kicks the same way MAD-X does; as MAD-X, an HKICKER (VKICKER) kicks by kick, or by hkick (vkick) when kick is zero — a deck may give either —, KICKER and TKICKER by hkick and vkick; chkick (cvkick), a corrector's setting, adds to the horizontal (vertical) kick; tilt rolls the kick about s; a flag without a value (kick, tilt, …) is 0 and an attribute MAD-X refuses on the type is not read, each with a warning) |
RCOLLIMATOR / ECOLLIMATOR |
Aperture (rectangular / circular; xsize, ysize are half-apertures) |
SEXTUPOLE |
thin Multipole between half drifts (k2·l; tilt → tilt_deg, a bare tilt = π/6; a skew component k2s turns it as MAD-X does: √(k2² + k2s²) at tilt − atan2(k2s, k2)/3) |
MULTIPOLE |
Multipole (knl, ksl; tilt → tilt_deg, same rotation sense as MAD-X's tilt and Quadrupole.skew_angle). In MAD-X a dipole component knl[0] or an angle makes the multipole a thin bend — the reference orbit turns there — and its TWISS takes ksl[0] as a vertical one. HELIX's Multipole has no curvature — the dipole part is a kick about a straight reference — and the import says so |
MATRIX |
MatrixElement (rm11…rm66 converted from MAD-X's canonical basis at the local reference energy and clock, about HELIX's reference orbit: the pt a map gives that orbit is the reference's energy gain, ref_gain, and the rest of its kicks an offset of every particle). The clock comes from the sequence's cavities, and with none it is 352.21 MHz; the element records it (clock_mhz) and, run at another frequency, gives the same map in that frequency's degrees. |
RFCAVITY |
RFGap (volt in MV; lag follows the MAD-X definition — reference gain V·sin(2π·lag), not multiplied by the charge, whereas HELIX gains q·V·T·cos φs — so the synchronous phase is 360·lag − 90°, plus 180° when volt and the charge have opposite signs; TTF = 1) |
SOLENOID |
Solenoid (ks → field via Bρ) |
MARKER |
Marker |
MONITOR / HMONITOR / VMONITOR |
Marker (BPM-flagged) |
Not supported (reported with a warning): CALL (the deck must be
self-contained — flatten it or inline the called files), tables and
SETVARS, MATCH / TRACK / TWISS and other commands, error and
alignment commands (EALIGN, EFCOMP), SEQEDIT, lines with formal
arguments, sequences that contain other sequences, a bend rolled by an
angle other than 0, ±π/2 or ±π (imported as a horizontal bend), the
curvature of thin bends written as MULTIPOLEs (see above), macros
nested more than 50 deep, and element types HELIX does not model
(OCTUPOLE, COLLIMATOR, … — kept as a marker or a drift of their
length).
One MAD-X quirk is not copied: MAD-X drops a block that follows another
statement on the same line (x = 1; if (…) {…}) when nothing comes after
it in the file; the importer runs it.
MAD-X lengths are in metres; HELIX converts them to its internal millimetres on import.
The
lagconvention is pinned against MAD-X itself (a tracked reference particle through oneRFCAVITY,tests/io/test_madx_conventions.py). Decks imported before 2026-09-03 carried every cavity 90° off (zero reference gain atlag=0); re-import them.
Exporting to MAD-X¶
The path also runs the other way: HELIX writes the loaded lattice as a
MAD-X SEQUENCE file, built as the exact inverse of the importer
above (same unit and rigidity conversions, so export → import gives the
elements of a line with no energy gain back to machine precision, in the
importer's clock). FREQ and energy cards are comments, which the importer
does not read back. Three entry points:
- GUI — File → Export Lattice as MAD-X…. The Beam tab's species
and energy set the rigidity every
k1/ksis normalised by, so the beam must be configured first; warnings are listed in the completion box and counted in the status bar. - CLI —
python -m linac_gen export deck.dat out.madx --energy 2.1 --species H-(--energyis required for a bare lattice; a.lgprojsupplies its Beam settings).--strictrefuses the export if any element has no MAD-X equivalent;--linearizewrites them asMATRIXelements instead (below);--sequence/--titlename the sequence and theTITLEline. See CLI. - Python:
from pathlib import Path
from linac_gen.cli.common import build_ref
from linac_gen.core.config import BeamConfig
from linac_gen.elements.quadrupole import Quadrupole
from linac_gen.io.madx_parser import parse_madx
from linac_gen.io.madx_writer import write_madx
from linac_gen.io.tracewin_parser import parse_tracewin
lattice, _ = parse_tracewin("examples/fodo_cell.dat")
ref = build_ref(BeamConfig(species="H-", energy=3.0, frequency=352.21))
warnings = write_madx(lattice, "fodo_cell.madx", ref) # → list[str]
assert warnings == []
back, meta = parse_madx("fodo_cell.madx")
quads = [e for e in lattice.elements if isinstance(e, Quadrupole)]
back_quads = [e for e in back.elements if isinstance(e, Quadrupole)]
assert [q.gradient for q in back_quads] == [q.gradient for q in quads]
Path("fodo_cell.madx").unlink()
The file carries a header comment (HELIX version, reference particle,
Bρ), a TITLE, the BEAM line, the element definitions, one
SEQUENCE … ENDSEQUENCE with refer=entry and explicit at= entry
positions, and a closing USE. Lengths are in metres, angles in
radians, k1 in 1/m², ks in 1/m, volt in MV, freq in MHz.
Element mapping (Bρ is the reference rigidity, signed by the charge
— an H⁻ deck exports with the k1 signs MAD-X expects for a negative
particle; H⁻ and deuterons become BEAM, particle=ion, mass=…,
charge=… since MAD-X has no named particle for them):
| HELIX | MAD-X |
|---|---|
Drift |
DRIFT |
Quadrupole |
QUADRUPOLE (k1 = G/Bρ; skew_angle → tilt; g3…g6 / gfr are not representable and warn) |
Edge + Dipole + Edge |
one SBEND (l = arc, signed angle, e1/e2 = sign(angle·ρ)·β from the pole rotations, k1 = −n/ρ², tilt=pi/2 for a vertical bend, hgap/fint/fintx from the edge gaps and K1s; a non-default edge K2 warns). SET_*/ADJUST_* cards between the edges and the body stay comments, a magnet split into consecutive BEND cards keeps its one edge pair, and edges whose ρ or plane disagree with the body are folded with a warning. A lone Dipole becomes an SBEND with e1=e2=0; RBEND is never written |
Solenoid |
SOLENOID (ks = B/Bρ) |
RFGap |
RFCAVITY, l=0 (volt = sign(q)·V·TTF — MAD-X does not multiply the RF kick by the charge and has no transit-time factor, so the sign carries the species and a TTF ≠ 1 is folded into volt with a warning; lag = (φs + 90°)/360°, the inverse of the import rule; freq in MHz) |
Steerer (magnetic) |
KICKER (hkick/vkick from ∫B·dl and the signed Bρ; electric steerers are not representable) |
Multipole |
MULTIPOLE, knl={…}, ksl={…}, tilt (same sense, see the import row). A dipole component knl[0] / ksl[0] is a kick about a straight reference in HELIX but a thin bend in MAD-X (see the import row): the export warns, and reading the file back gives the same kick |
Aperture |
RCOLLIMATOR / ECOLLIMATOR with both xsize/ysize (MAD-X TRACK losses) and apertype/aperture (its APERTURE module); the rectangular family (types 0, 3, 4, 5) is rectangular, type 1 circular; pepperpot and ring are not representable |
Marker / BPM marker |
MARKER / MONITOR |
FREQ, SET_*, ADJUST_*, DIAG_*, LATTICE, SPACE_CHARGE_COMP, … |
a ! HELIX: … comment at the element's position — nothing vanishes silently |
Not representable in MAD-X — field maps (FIELD_MAP,
SUPERPOSE_MAP), NCELLS, RFQ cells, foils, explicit-matrix
elements, thin lenses and electric steerers. By default each becomes a
MARKER plus a body DRIFT of the element's length (the geometry
survives) and a warning naming it; write_madx(…,
on_unsupported="error") / export --strict refuses the export
instead. A curved field-map cluster (SUPERPOSE_MAP_OUT) also warns
that its bend cannot be kept — the MAD-X line continues straight after
it — and the strict export refuses it. Misalignments (dx, dy, tilt) are not exported and warn —
MAD-X carries them through EALIGN, which this writer does not emit.
Linearised export (write_madx(…, linearize=True) /
export --linearize): field maps, NCELLS, RFQ cells, thin lenses and
explicit-matrix elements are written as MAD-X MATRIX elements built
from their first-order map at the local reference energy and clock (the
map the matrix mode uses, up to an energy card, which the export stays
before, and a misalignment, which it warns of as for every element),
converted to MAD-X's canonical basis
(x, px, y, py, t, pt — t = −cΔt, pt = ΔE/p₀c, both pinned
against MAD-X TRACK). An NCELLS map carries TraceWin's matrix-engine
synchronism factor where it acts (βg > 0 and no βs≠0 transit-time tail;
it is skipped for a cavity with no field and far off its geometric β),
which the multiparticle tracker does not apply. The export says so for
each such cavity; setting the cavity's synchronism attribute to False
in Python gives the tracker's map. t holds no clock, so each side of a map
converts with the clock at its point, and a FREQ card stays a comment
because it is nothing in these coordinates. MAD-X cannot move its reference energy along a
sequence, so an accelerating element's map is written about the entrance
momentum with the exit angles rescaled by p₀/p_out (the canonical,
symplectic form). Each map is written about HELIX's reference orbit in
MAD-X's coordinates: pt the energy the reference has gained over p₀c,
and t restarting at each MATRIX exit and growing by L·(1/β₀ − 1/β)
over the length between. The kicks take that orbit through the map (the
gain is in kick6), so a particle's coordinates at a map's exit are its
HELIX ones, MAD-X TRACK of the reference ends on the orbit, and the
import reads the gain back as the reference's. Two elements after a gain
are tied to MAD-X's fixed p₀ and act differently on the faster orbit: an
SBEND bends it by θ/(1+δ), where HELIX's dipole bends its reference by θ,
and an RFCAVITY's lag is set for p₀'s arrival, not the orbit's. The
export rescales neither, so after one of them TRACK's reference leaves the
orbit (and a deck from elsewhere with a bend after a kick6 reads with the
bend set for the new energy). An explicit matrix element's energy offset
comes back as the reference's gain. MAD-X TWISS
symplectifies MATRIX elements, so a map that is not symplectic (a
non-Maxwellian synthetic field, a coarse fit) is flagged — TRACK
applies it verbatim. Foils and electric steerers still fall under the
unsupported rule.
pt is a deviation about the entrance momentum. TRACK is exact
about the orbit, but MAD-X TWISS expands the drifts between the maps to
second order about it, and its R56 is off by about 7.5·(pt/β₀)²
(measured): 0.1 % at pt/β₀ = 0.0115, 1 % at 0.037, 10 % at 0.12. The
exporter reports the total linearised gain as pt and pt/β₀, with the
estimate above 0.1 % and a warning that TWISS will not reproduce the line
above 10 %; for a whole linac export one section at a time from its own
entrance energy. ERROR_* cards (an error study, not lattice elements)
are not exported in either format — MAD-X models tolerances through
EALIGN/EFCOMP — and the writer says so.
Limits inherited from MAD-X. A sequence has one BEAM energy: every
k1/ks is normalised by the entrance rigidity (the exact inverse of
the importer), TWISS does not follow the energy gain of an
accelerating line (TRACK sees it as pt), and RFCAVITY carries no
transverse RF (de)focusing — an accelerating deck therefore exports with
a one-line warning and MAD-X reproduces its optics only up to the first
cavity. The HELIX energy card (; HELIX_SET_ENERGY, ; HELIX_SHIFT_ENERGY)
stays a comment, and the line keeps the energy before the card; one that would
move the energy warns, and --strict refuses it. SET_BEAM_ENERGY and
SET_BEAM_E0_P0, matching criteria, stay comments as well.
MAD-X sequences cannot step backwards, so a negative-length
drift refuses the export, as does a zero-length lattice. Element names
are made MAD-X-legal (illegal characters, leading digits, reserved
command/class names such as start or wire, names longer than 40
characters, and a clash with the sequence name — the last two crash
MAD-X outright rather than erroring).
Verified against MAD-X 5.09.03 through
cpymad: every public example deck loads in MAD-X, MAD-X's own cumulative R-matrix for the exported line equals HELIX's transfer matrix (transverse 4×4, both proton and H⁻), and the bundledexamples/madx/*.madxfiles survive a MAD-X → HELIX → MAD-X round trip with the same Twiss (tests/io/ test_madx_oracle.py; the tests skip whencpymadis not installed).
Importing MAD8 lattices (.lat, .flat, .mad8)¶
HELIX reads MAD8 lattices (.lat / .flat / .mad8): SAVELINE
flat files such as the PIP-II BTL and BAL exports, hand-written decks,
and MAD8 files written by other codes. Use Open Lattice… in the GUI,
any CLI command, or:
from linac_gen.io.mad8_parser import parse_mad8
lattice, meta = parse_mad8("BTL2025v0703.lat") # → (Lattice, metadata)
Language.
!comments and&continuations. As in MAD8, text after the&on the same line is ignored.;separates statements on one line.COMMENT…ENDCOMMENTblocks.CALL, FILENAME=…reads another file (the path is taken relative to the calling file first).RETURNandSTOP.- Parameters:
name := expr,name = expr,name: CONSTANT = exprandSET, name, expr. They are evaluated when first used, so their order in the file does not matter. If a name is defined twice, the last definition wins and a warning lists it. - Expressions:
+ − * / ^, FortranDexponents andNAME[ATTR]references to element attributes (alsoBEAM[…]). - Functions: the MAD8 set
SQRT LOG EXP SIN COS TAN ASIN ABS MAX MIN, plusACOS ATAN SINH COSH TANH LOG10. - Constants, with MAD8's values:
PI TWOPI DEGRAD RADDEG E EMASS PMASS CLIGHT. RANF(),GAUSS()andTGAUSS()take their mean value, with a warning. HELIX imports the nominal machine.- Elements:
name: CLASS, attr=expr, …. - MAD8 keyword abbreviations work (
QUAD,SEXT,RFCAV, …). - Class inheritance works (
QF2: QF, K1=…), and so do attribute changes (QF, K1=…). - Word attributes such as
TYPE=…are kept out of the arithmetic. - Beam lines:
name: LINE = (…)with-NAME,N*NAME, nested groupsN*(A, -(B, C)), and lines with arguments (CELL(X, Y): LINE = (X, D, Y)used asCELL(QF, QD)). name: SEQUENCE[, REFER=…, L=…]…ENDSEQUENCE[, AT=…]withAT/FROMplacement (FROMcounts from the earlier member's centre, as MAD-X does). The drifts between members are generated.USE, nameselects the beam line. Without it, the importer takes the largest line that no other line references, and warns.parse_mad8(..., line="NAME")overrides both.- Action commands (
TWISS,MATCH…ENDMATCH,SELECT,EALIGN, …) are skipped and listed in one warning. Misalignment and field-error commands (EALIGN,EFIELD, …) are not applied. Model errors with the Error Study instead.
Elements. The element mapping follows the MAD-X table above, plus these MAD8 cases:
| MAD8 | HELIX |
|---|---|
KICKER / HKICKER / VKICKER with zero kick |
Marker + a Drift of the full length (the BTL/BAL corrector layout) |
| the same with a kick | Steerer between half-length drifts. TILT rotates the kick. |
plain MONITOR (also IMONITOR, WIRE, PROFILE, …) |
non-BPM Marker + body Drift |
H/VMONITOR |
BPM marker |
TILT on a quadrupole |
skew_angle (a bare TILT = 45°) |
TILT=±π/2 on a bend |
a vertical bend (hv=1) |
OCTUPOLE |
thin Multipole (k3·L) between half-length drifts |
MULTIPOLE, KnL=…, Tn=… |
knl[n] = KnL·cos((n+1)Tn), ksl[n] = −KnL·sin((n+1)Tn). A bare Tn = π/(2(n+1)). |
zero-angle SBEND/RBEND with K1 |
Quadrupole (exact: with no curvature the bend map is the quadrupole map) |
RFCAVITY with HARMON |
frequency = HARMON·βc/C, where C is the length of the line used |
RFCAVITY with neither HARMON nor FREQ |
a Drift of its length (the cavity has no frequency) |
LCAVITY (DELTAE MeV, PHI0 in turns, FREQ MHz) |
RFGap with gain DELTAE·cos 2πPHI0. Magnets after it are converted with the local rigidity. |
LUMP, LINE=… |
the referenced line, expanded in place |
anything else (ELSEPARATOR, WIGGLER, BEAMBEAM, SROT, …) |
a Drift of its length, or a Marker when L = 0. One warning per class says the field is not modelled. |
A multipole's dipole term K0L is applied as an orbit kick about a
straight reference, as the HELIX MAD-X importer also does. MAD-X instead
bends the reference orbit by K0L. A file with a non-zero K0L gets a
warning that says so.
Rigidity and charge sign. MAD strengths are normalized (K1 = (q/p)·∂B/∂x). HELIX stores the lab-frame gradient, so the conversion is G = sign(q)·K1·|Bρ|. For H⁻ every gradient sign flips relative to a proton import. Bρ comes from, in this order:
- a
BEAMstatement that names the particle or its energy:PARTICLEand/orMASS+CHARGE, withENERGY(total, GeV),PCorGAMMA. MAD8's defaults apply: positron, 1 GeV. ABEAMthat carries only emittances orNPARTis not a rigidity source.BEAM[PC],BEAM[GAMMA]and the other derived quantities can be referenced in expressions. - the
brho=argument. - a
BRHO := …parameter in the file. Its species is thespecies=argument, else the fallback beam's species, else H⁻. For H⁻ it is refused when it implies an impossible energy (above 20 GeV). Some BAL exports writeP0/C*1.0E11with P0 in MeV/c and c in cm/s, which is 1000 times too large. - the fallback beam: the project's beam, or the Beam tab when a bare lattice is opened in the GUI. It is used only when the file declares no usable rigidity, and a warning (plus a note in the GUI status bar) says so.
With none of these the import fails loudly: a wrong Bρ would
silently mis-scale every magnet. The CLI only has a fallback when it is
given a project (.lgproj), and a scan converts the magnets with the
project's beam as saved, never with the per-point --beam overrides.
At GUI start-up a restored lattice uses the last session's beam. A BEAM particle that
HELIX does not model (electron, positron, antiproton, …) keeps its own
rigidity for the magnets, and the tracking reference becomes a proton
at that rigidity. The magnetic optics then match MAD8, and a warning
states the substitution. After an LCAVITY the magnets follow the real
particle's momentum; a tracked proton through the same cavity would not,
and a warning says so. A warning always states the reference used,
so set the Beam tab to match before running.
Checked against MAD-X. On every corpus lattice MAD-X can read, the
imported transverse transfer matrix matches MAD-X's reading of the same
file to ≤ 6·10⁻⁸, and to 10⁻¹⁰ or better on most. For this comparison
RF voltages and kicks are zeroed in both codes: MAD-X's twiss neither
accelerates the reference nor linearises about a kicked orbit the way
HELIX's matrix does. The files checked are the Fermilab Main Injector
(fractional tunes 0.4252865423 / 0.4152846703 in both codes), the CSNS RCS, two
SNS ring decks, the PIP-II BAL, BTL2022, an electron-lens FODO series
and a 10 GeV electron ring written with keyword abbreviations and
nested groups. tests/io/test_mad8_parser.py carries the MAD-X
comparisons; they skip when cpymad is not installed.
Automatic periodicity. The .lat LINE hierarchy declares the
machine's cell structure, which a flat TraceWin file loses. The
importer identifies repeated cells (LINE-valued entries of the root
line's sections with identical transport signatures and significant
element counts) and declares them as LATTICE n 0 / LATTICE_END
brackets automatically, so per-cell phase advance σ₀, tune depression
η, and the Hofmann chart work immediately. Disable with
parse_mad8(..., auto_periods=False).
HELIX never writes MAD files: with a
.lat(or.madx) lattice loaded, Save routes to Save As… (TraceWin.dat) instead of overwriting the source. DeclaredLATTICEbrackets survive the.datexport and round-trip.
Importing Elegant lattices (.lte)¶
HELIX also reads Elegant lattice-element files (.lte) —
Open Lattice… in the GUI (filter Elegant (*.lte)), any CLI
<input>, or:
from linac_gen.io.elegant_parser import parse_elegant
lattice, meta = parse_elegant("ring.lte", species="H-",
w_kin=1000.0, frequency=352.21)
Supported: !/# comments, & and trailing-comma continuation;
line=(...) beamlines with leading-- reversal and N*elem
repetition; element templates (a type token that names a defined
element inherits its attributes) and name[prop]=value overrides.
Lengths in metres, angles in radians, k1/k2 in MAD-normalised
units, cavity volt in V, phase in degrees (90° = crest), freq in
Hz — the unit and rigidity conversions are shared with the MAD-X
importer above. An order-1 ematrix imports verbatim as a
MatrixElement (explicit 6×6 map; the longitudinal block is kept in
Elegant's (t, p) basis — no basis change is applied, and the importer
warns in metadata["warnings"] whenever that block is non-trivial:
a non-identity longitudinal 2×2, transverse↔longitudinal coupling, or
a longitudinal offset. A purely transverse map imports exactly,
without a warning).
Degrades explicitly, never silently: CSR/LSC drifts → Drift
(+warning); charge/wake → Marker (+warning); unknown types →
Drift (+warning). .lte files carry no beam energy (it lives in
the run file), so pass species / w_kin / frequency — the
strength conversion is self-consistent for any value.
Import-only: with an
.ltelattice loaded, Save routes to Save As… (TraceWin.dat) and never overwrites the Elegant source.
Importing Bmad, SciBmad and PALS lattices (via lattix)¶
Bmad lattices (.bmad), SciBmad lattices — Julia source for the
Beamlines.jl package (.jl / .scibmad) — and PALS documents
(.pals.yaml / .pals.yml / .pals.json, the Particle Accelerator
Lattice Standard) open like any other lattice: Open Lattice… in the
GUI (filters Bmad, SciBmad, PALS), any CLI <input>, the
Import an existing lattice page of the New Project wizard, the
assistant's load_lattice tool, or:
from linac_gen.io.lattix_bridge import parse_with_lattix
lattice, meta = parse_with_lattix("examples/scibmad/fodo.jl") # or .bmad / .pals.yaml
lattice, meta = parse_with_lattix("examples/bmad/fodo.bmad", species="proton")
for w in meta["warnings"]: # the fidelity ledger
print(w)
These formats are read by the lattix translator, an optional
dependency that is not yet published: install its checkout into the same
environment, or point HELIX_LATTIX_ROOT at it (a Translator checkout
next to the HELIX repository is found without either). Without it the
import fails with a message that says so;
nothing else in HELIX depends on it. lattix parses the file into its own
code-neutral representation and its HELIX adapter builds the Lattice
from that — the deck is parsed, never run: a SciBmad file is read by
a pure-Python parser of the restricted Julia subset lattice files use
(using Beamlines, @elements begin … end, element constructors with
keyword arguments, Beamline/Branch, simple arithmetic and DefExpr);
Julia is not needed and statements outside that subset are reported by
line, not executed.
Units and conventions. Lengths in metres, angles in radians.
Normalised strengths (k1, Kn1, Ksol, …) are converted to HELIX
gradients with the signed rigidity of the deck's own reference
particle (Bmad parameter[particle]/p0c, Beamlines species_ref +
E_ref/pc_ref, PALS ReferenceP); meta["warnings"] ends with the
same set the Beam tab to match note the MAD8 importer prints. Bmad
lcavity/rfcavity phases and Beamlines phi0 (radians, crest = 0)
become the equivalent raw RF phase of a HELIX GAP; a thick cavity
becomes drift–gap–drift; a Bmad patch becomes a marker; the
reference-energy jumps of a SciBmad Branch (dE_ref) follow the
cavities. Every approximation and every dropped element is listed by
name in meta["warnings"] (and meta["fidelity"] keeps both of lattix's
reports in full) — the same ledger the CLI echoes as parse warning: and
the GUI counts in the status bar.
Species. HELIX tracks protons, H⁻ and deuterons. A deck whose
reference particle is something else — Bmad's default is the
positron — is refused with a message naming the deck's reference;
re-open it with species="proton" (h-, deuteron) and the strengths
are converted for that particle at the deck's momentum. H⁻ flips the
sign of every normalised strength, as the deck's own code would.
The three bundled examples examples/bmad/fodo.bmad,
examples/scibmad/fodo.jl and examples/pals/fodo.pals.yaml are the
FODO cell of examples/madx/fodo.madx written out by lattix; imported,
each gives the same HELIX lattice as the MAD-X importer. A SciBmad file
carries its reference particle on a leading Marker(species_ref = …,
E_ref = …); that marker is imported as a zero-length Marker (so the
.jl version of the cell has one element more than the .bmad one, and
element indices are shifted by one).
Import-only: with a Bmad / SciBmad / PALS lattice loaded, Save routes to Save As… (TraceWin
.dat) and never overwrites the source; the New Project wizard materialises such a deck as<name>.datinside the project.
Importing TraceWin project settings (.ini)¶
A TraceWin project is a .dat deck and a <project>.ini — the
binary options file that holds what was typed into TraceWin's Main
and Beam panels: particle, energy, frequency, current, macro-particle
count, the normalised emittances and the Twiss parameters of both input
beams, the PICNIC meshes. HELIX decodes it, so a deck can start from
the beam it was designed for instead of the stock BeamConfig
default (3 MeV proton) — the mistake behind more than one "why is the
800 MeV line lost at 2 MeV" run.
When to import it, and how¶
Use the .ini whenever you have a TraceWin project — a deck and
its options file, typically a folder handed over from a TraceWin user
or exported from TraceWin — and you want HELIX to start from the beam
that deck was designed for. Do not look for one when you only have a
bare .dat (a deck written by hand, by HELIX, or converted from
MAD-X/MAD8/Elegant carries no .ini), and do not expect it to be read
when you open a .lgproj: a saved project already holds the beam you
chose, and it always wins.
| Situation | Route | What happens |
|---|---|---|
You open a TraceWin .dat in the GUI and <deck>.ini sits next to it |
File → Open Lattice… | HELIX asks Import its beam? — Yes fills the Beam tab and makes it the session beam; No keeps the current beam (Lattice tab) |
The deck is already open, or the .ini lives elsewhere / under another name |
Beam tab → Import TraceWin .ini… | pick the file; the form is replaced and the project marked dirty (Beam tab) |
| You are creating a HELIX project from a TraceWin deck | File → New Project… → Import an existing lattice | tick Also import the beam from the TraceWin .ini next to the deck; the .ini is copied with the deck and the .lgproj is written with its beam (workflows) |
| You want a HELIX project file from the shell | python -m linac_gen twini deck.ini --lgproj |
writes deck.lgproj next to the .ini, pointing at deck.dat |
| You want one headless run / scan / study without a project | python -m linac_gen run deck.dat --tracewin-ini … (also scan, batch, twiss, backtrack, failures, mo, export, orm) |
the beam is read for that command only; --energy etc. still override |
| You only want to see what the file says | python -m linac_gen twini deck.ini --report or the assistant's inspect_tracewin_ini |
nothing is loaded or written |
Step by step (command line):
-
Look before you load — the report lists what will be applied, what is recorded only, and what the file does not describe:
Check the particle row (an ion or a
My_particleslot has no HELIX species — pass--species), the energy and frequency, and the Warnings block (aFREQmismatch with the deck, a DC beam, a renamed file). 2. Either turn the pair into a HELIX project once —— and work from
deck.lgprojfrom then on (GUI File → Open Project…, or any CLI command with the.lgprojas input); or use the.inifor a single command: -
Set what the
.inidoes not carry: the distribution type and cut-off, a DC beam's energy spread, centroid offsets. On the CLI--beam distribution=gaussian --beam cutoff=3; in a project, edit the Beam tab and Save Project. - Run. The first
FREQcard of the deck is compared with the beam frequency and any difference is warned about; HELIX rescales the longitudinal plane at the card.
After an import, check the Beam tab (or the printed summary): the
species, energy and current are the project's; alpha_z already
carries HELIX's sign (do not flip it again); a DC project shows
Continuous beam ticked; a value outside the form's range (say
5 000 000 particles) was clamped and reported; the console lists every
warning. Then Save Project — until you do, the imported beam lives
only in the session.
Commands¶
python -m linac_gen twini deck.ini # the converted beam + warnings
python -m linac_gen twini deck.ini --report # every decoded slot: applied / recorded / unknown / not decoded
python -m linac_gen twini deck.ini --json # machine-readable
python -m linac_gen twini deck.ini --lgproj # write deck.lgproj next to the .ini
python -m linac_gen twini deck.ini --lgproj my.lgproj --lattice other.dat --beam 2 --species H- --force
twini prints the converted beam, and with --lgproj writes a project
file — the same format the GUI saves — that points at the sibling
deck.dat (or --lattice). The deck is parsed for the check that its
first FREQ card matches the beam frequency. The same conversion is
one call in Python:
from linac_gen.io.tracewin_ini import load_tracewin_ini, to_beam_config, report
ini = load_tracewin_ini("tests/io/fixtures/tracewin_ini/ads.ini")
beam, warnings = to_beam_config(ini) # beam 1 → BeamConfig in HELIX units
print(beam.species, beam.energy, beam.emit_nx, beam.alpha_z, beam.beta_z)
print(report(ini)) # applied / recorded / unknown / not decoded
Precedence. A .lgproj always wins: its beam is what you saved, and
the .ini is read only when you ask for it (twini, or the
--tracewin-ini option on the CLI runners). Nothing reads a .ini
silently. Scalar overrides (--energy, --freq, --species) still
apply on top of the imported beam, with a warning: emit_z and
beta_z were converted at the .ini energy, frequency and species and
are not re-derived for the overridden values.
Applied to the beam (every slot verified against at least two
independent projects, the LightWin ads.ini ↔ σ-matrix identity and a
TraceWin run — see Appendix G
for the offsets and the evidence): species from the particle-table row
(proton, deuteron, H-), energy, frequency, current, n_particles,
emit_nx/emit_ny (normalised, π mm mrad), alpha_x/beta_x,
alpha_y/beta_y, and the longitudinal plane converted into HELIX's
(Δφ, ΔW) units:
| HELIX | from the .ini |
|---|---|
emit_z (π deg MeV) |
eps_z1 · 360 · f · mc² / c |
beta_z (deg/MeV) |
betz1 · 360 · f / ((βγ)³ · c · mc²) |
alpha_z |
−alpz1 — the usual TraceWin sign flip, applied once, here |
For the ADS sample: 3e-7 π m rad → 0.0338 π deg MeV, 2.5932 m/rad → 37.11 deg/MeV, α_z +0.17661 → −0.17661.
Identified, not applied: thread count and the PICNIC r/z and x/y
meshes are probable slots and, more to the point, not HELIX's
space-charge grid — they are printed in the report and recorded under
convergence.tracewin_ini of a written project, never applied.
Degrades explicitly, never silently: a DC beam (eps_z1 = 0, LEBT
projects) → continuous = True with a warning that the energy spread
is not decoded; a particle row with no HELIX species (an ion, a
My_particle slot) → your --species or proton, with a warning that
names the row and says the .ini rest mass was not used; a bunched beam
with betz1 = 0, an unpopulated beam 2, a zero transverse emittance →
refused; a file of a size other than the two known layouts (31 624 and
44 824 bytes) or a wrong magic → refused with the known sizes named (a
layout tag that disagrees with the size — TraceWin's own *.old.ini —
is a warning, not a refusal). The distribution type,
duty cycle, energy spread, centroid offsets and .dst path are not
decoded; the report lists them so you set them yourself.
Cross-references¶
- Keyword cheatsheet — one-page printable.
- Appendix G — the TraceWin
.iniformat. - Element catalog.
- Matching SET/ADJUST.
- Errors directives.