Skip to content

Commit aa1b320

Browse files
committed
Translator: Simplify for Maintainability
1 parent 770e5ea commit aa1b320

File tree

1 file changed

+100
-88
lines changed

1 file changed

+100
-88
lines changed

src/python/impactx/madx_to_impactx.py

Lines changed: 100 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .MADXParser import MADXParser
1515

1616
WARN_PREFIX = "[WARNING MADX-ImpactX-Translator] "
17+
TRACKED_TRANSLATION_OPTIONS = ("rbarc", "thin_foc")
1718

1819

1920
class MADXImpactXTranslatorWarning(UserWarning):
@@ -64,6 +65,12 @@ def lattice(parsed_beamline, nslice=1, freq0=0.0, options=None):
6465
:param nslice: number of ds slices per element
6566
:param freq0: revolution frequency in MHz (from BEAM command)
6667
:return: list of translated dictionaries
68+
69+
Maintainer note (one-place-of-truth philosophy):
70+
- Keep element-type mapping in one place (`madx_to_impactx_dict` below).
71+
- Keep bend-body model choice in one place (`make_bend_body_element` below).
72+
- Prefer composition from existing ImpactX elements over silent drops.
73+
- Every physics fallback should `_warn(...)` and include a TODO comment.
6774
"""
6875

6976
# mapping is "MAD-X": "ImpactX",
@@ -104,6 +111,10 @@ def lattice(parsed_beamline, nslice=1, freq0=0.0, options=None):
104111
# rbarc=true, thin_foc=true
105112
rbarc = bool(options.get("rbarc", True))
106113
thin_foc = bool(options.get("thin_foc", True))
114+
rad_to_deg = 180.0 / math.pi
115+
# MAD-X bend maps are driven by ANGLE/TILT; K0/K0S are obsolete for map construction.
116+
# H1/H2 (pole-face curvature) still affect MAD-X fringe modeling and are not translated here.
117+
unsupported_bend_attrs = ("k0", "k0s", "h1", "h2")
107118
warned_once = set()
108119

109120
class _TrackedElement(dict):
@@ -163,6 +174,55 @@ def append_thin_with_length(thin_element, length, element_name):
163174
else:
164175
impactx_beamline.append(thin_element)
165176

177+
def make_bend_body_element(
178+
*,
179+
name,
180+
ds,
181+
rc,
182+
tilt_degree,
183+
k1,
184+
k1s,
185+
k2,
186+
k2s,
187+
k3,
188+
k3s,
189+
):
190+
"""Create the most faithful currently available ImpactX bend body model.
191+
192+
Priority:
193+
1) ExactCFbend when skew or higher body multipoles are present
194+
2) CFbend for pure dipole+quadrupole
195+
3) Sbend for pure dipole geometry
196+
"""
197+
use_exact_cfbend = any(abs(v) > 0.0 for v in (k1s, k2, k2s, k3, k3s))
198+
if use_exact_cfbend:
199+
curvature = 1.0 / rc if rc != 0.0 else 0.0
200+
return elements.ExactCFbend(
201+
name=name,
202+
ds=ds,
203+
k_normal=[curvature, k1, k2, k3],
204+
k_skew=[0.0, k1s, k2s, k3s],
205+
unit=0,
206+
rotation=tilt_degree,
207+
nslice=nslice,
208+
)
209+
if abs(k1) > 0.0:
210+
return elements.CFbend(
211+
name=name,
212+
ds=ds,
213+
rc=rc,
214+
k=k1,
215+
rotation=tilt_degree,
216+
nslice=nslice,
217+
)
218+
return elements.Sbend(
219+
name=name,
220+
ds=ds,
221+
rc=rc,
222+
rotation=tilt_degree,
223+
nslice=nslice,
224+
)
225+
166226
for d_raw in parsed_beamline:
167227
d = _TrackedElement(d_raw)
168228
# print(d)
@@ -177,7 +237,7 @@ def append_thin_with_length(thin_element, length, element_name):
177237
ds = d.get("l", 0.0)
178238
k1 = d.get("k1", 0.0)
179239
k1s = d.get("k1s", 0.0)
180-
tilt_degree = d.get("tilt", 0.0) * 180.0 / math.pi
240+
tilt_degree = d.get("tilt", 0.0) * rad_to_deg
181241
if ds > 0:
182242
if abs(k1s) > 0:
183243
impactx_beamline.append(
@@ -231,7 +291,7 @@ def append_thin_with_length(thin_element, length, element_name):
231291
hgap = d.get("hgap", 0.0)
232292
fint = d.get("fint", 0.0)
233293
fintx = d.get("fintx", fint)
234-
tilt_degree = d.get("tilt", 0.0) * 180.0 / math.pi
294+
tilt_degree = d.get("tilt", 0.0) * rad_to_deg
235295
if ds > 0:
236296
if abs(angle) > 0:
237297
rc = ds / angle
@@ -256,45 +316,20 @@ def append_thin_with_length(thin_element, length, element_name):
256316
)
257317
)
258318

259-
use_exact_cfbend = any(
260-
abs(v) > 0.0 for v in (k1s, k2, k2s, k3, k3s)
261-
)
262-
if use_exact_cfbend:
263-
# ExactCFbend can represent curvilinear combined-function
264-
# bends including sextupole/octupole body terms.
265-
curvature = 1.0 / rc if rc != 0.0 else 0.0
266-
impactx_beamline.append(
267-
elements.ExactCFbend(
268-
name=d["name"],
269-
ds=ds,
270-
k_normal=[curvature, k1, k2, k3],
271-
k_skew=[0.0, k1s, k2s, k3s],
272-
unit=0,
273-
rotation=tilt_degree,
274-
nslice=nslice,
275-
)
276-
)
277-
elif abs(k1) > 0:
278-
impactx_beamline.append(
279-
elements.CFbend(
280-
name=d["name"],
281-
ds=ds,
282-
rc=rc,
283-
k=k1,
284-
rotation=tilt_degree,
285-
nslice=nslice,
286-
)
287-
)
288-
else:
289-
impactx_beamline.append(
290-
elements.Sbend(
291-
name=d["name"],
292-
ds=ds,
293-
rc=rc,
294-
rotation=tilt_degree,
295-
nslice=nslice,
296-
)
319+
impactx_beamline.append(
320+
make_bend_body_element(
321+
name=d["name"],
322+
ds=ds,
323+
rc=rc,
324+
tilt_degree=tilt_degree,
325+
k1=k1,
326+
k1s=k1s,
327+
k2=k2,
328+
k2s=k2s,
329+
k3=k3,
330+
k3s=k3s,
297331
)
332+
)
298333

299334
if has_edges:
300335
impactx_beamline.append(
@@ -340,7 +375,7 @@ def append_thin_with_length(thin_element, length, element_name):
340375
impactx_beamline.append(
341376
elements.ThinDipole(name=d["name"], theta=angle, rc=0.0)
342377
)
343-
for attr in ("k0", "k0s", "h1", "h2"):
378+
for attr in unsupported_bend_attrs:
344379
if abs(d.get(attr, 0.0)) > 0:
345380
# TODO(audit): Map higher-order/extended SBEND attributes when
346381
# ImpactX provides a direct equivalent in this translator path.
@@ -362,7 +397,7 @@ def append_thin_with_length(thin_element, length, element_name):
362397
k2s = d.get("k2s", 0.0)
363398
k3 = d.get("k3", 0.0)
364399
k3s = d.get("k3s", 0.0)
365-
tilt_degree = d.get("tilt", 0.0) * 180.0 / math.pi
400+
tilt_degree = d.get("tilt", 0.0) * rad_to_deg
366401

367402
if l_chord > 0:
368403
# RBARC option controls how RBEND L is interpreted:
@@ -397,43 +432,20 @@ def append_thin_with_length(thin_element, length, element_name):
397432
)
398433

399434
# Body: prefer combined-function model if representable.
400-
use_exact_cfbend = any(
401-
abs(v) > 0.0 for v in (k1s, k2, k2s, k3, k3s)
402-
)
403-
if use_exact_cfbend:
404-
curvature = 1.0 / rc if rc != 0.0 else 0.0
405-
impactx_beamline.append(
406-
elements.ExactCFbend(
407-
name=d["name"],
408-
ds=l_arc,
409-
k_normal=[curvature, k1, k2, k3],
410-
k_skew=[0.0, k1s, k2s, k3s],
411-
unit=0,
412-
rotation=tilt_degree,
413-
nslice=nslice,
414-
)
415-
)
416-
elif abs(k1) > 0:
417-
impactx_beamline.append(
418-
elements.CFbend(
419-
name=d["name"],
420-
ds=l_arc,
421-
rc=rc,
422-
k=k1,
423-
rotation=tilt_degree,
424-
nslice=nslice,
425-
)
426-
)
427-
else:
428-
impactx_beamline.append(
429-
elements.Sbend(
430-
name=d["name"],
431-
ds=l_arc,
432-
rc=rc,
433-
rotation=tilt_degree,
434-
nslice=nslice,
435-
)
435+
impactx_beamline.append(
436+
make_bend_body_element(
437+
name=d["name"],
438+
ds=l_arc,
439+
rc=rc,
440+
tilt_degree=tilt_degree,
441+
k1=k1,
442+
k1s=k1s,
443+
k2=k2,
444+
k2s=k2s,
445+
k3=k3,
446+
k3s=k3s,
436447
)
448+
)
437449
# Exit DipEdge
438450
# Exit DipEdge
439451
impactx_beamline.append(
@@ -461,7 +473,7 @@ def append_thin_with_length(thin_element, length, element_name):
461473
impactx_beamline.append(
462474
elements.ThinDipole(name=d["name"], theta=angle, rc=0.0)
463475
)
464-
for attr in ("k0", "k0s", "h1", "h2"):
476+
for attr in unsupported_bend_attrs:
465477
if abs(d.get(attr, 0.0)) > 0:
466478
# TODO(audit): Map higher-order/extended RBEND attributes when
467479
# ImpactX provides a direct equivalent in this translator path.
@@ -473,7 +485,7 @@ def append_thin_with_length(thin_element, length, element_name):
473485
ks = d.get("ks", 0.0)
474486
ksi = d.get("ksi", 0.0)
475487
lrad = d.get("lrad", 0.0)
476-
tilt_degree = d.get("tilt", 0.0) * 180.0 / math.pi
488+
tilt_degree = d.get("tilt", 0.0) * rad_to_deg
477489
if ds > 0:
478490
impactx_beamline.append(
479491
elements.Sol(
@@ -528,14 +540,14 @@ def append_thin_with_length(thin_element, length, element_name):
528540
g=2.0 * d.get("hgap", 0.0),
529541
K2=d.get("fint", 0.0),
530542
location=location,
531-
rotation=d.get("tilt", 0.0) * 180.0 / math.pi,
543+
rotation=d.get("tilt", 0.0) * rad_to_deg,
532544
)
533545
)
534546
elif d["type"] in ("kicker", "tkicker"):
535547
# Corner case: ImpactX Kicker is thin. For MAD-X finite L, we preserve
536548
# placement/length with a drift-kick-drift composition.
537549
ds = d.get("l", 0.0)
538-
tilt_degree = d.get("tilt", 0.0) * 180.0 / math.pi
550+
tilt_degree = d.get("tilt", 0.0) * rad_to_deg
539551
if abs(d.get("sinkick", 0.0)) > 0:
540552
# TODO(audit): Map sinusoidally driven kicker modes (SINKICK/SINPEAK/SINTUNE/SINPHASE).
541553
_warn(
@@ -553,7 +565,7 @@ def append_thin_with_length(thin_element, length, element_name):
553565
)
554566
elif d["type"] == "hkicker":
555567
ds = d.get("l", 0.0)
556-
tilt_degree = d.get("tilt", 0.0) * 180.0 / math.pi
568+
tilt_degree = d.get("tilt", 0.0) * rad_to_deg
557569
if abs(d.get("sinkick", 0.0)) > 0:
558570
# TODO(audit): Map sinusoidally driven kicker modes (SINKICK/SINPEAK/SINTUNE/SINPHASE).
559571
_warn(
@@ -571,7 +583,7 @@ def append_thin_with_length(thin_element, length, element_name):
571583
)
572584
elif d["type"] == "vkicker":
573585
ds = d.get("l", 0.0)
574-
tilt_degree = d.get("tilt", 0.0) * 180.0 / math.pi
586+
tilt_degree = d.get("tilt", 0.0) * rad_to_deg
575587
if abs(d.get("sinkick", 0.0)) > 0:
576588
# TODO(audit): Map sinusoidally driven kicker modes (SINKICK/SINPEAK/SINTUNE/SINPHASE).
577589
_warn(
@@ -601,7 +613,7 @@ def append_thin_with_length(thin_element, length, element_name):
601613
ds = d.get("l", 0.0)
602614
k2 = d.get("k2", 0.0)
603615
k2s = d.get("k2s", 0.0)
604-
tilt_degree = d.get("tilt", 0.0) * 180.0 / math.pi
616+
tilt_degree = d.get("tilt", 0.0) * rad_to_deg
605617
if ds > 0:
606618
impactx_beamline.append(
607619
elements.ExactMultipole(
@@ -633,7 +645,7 @@ def append_thin_with_length(thin_element, length, element_name):
633645
ds = d.get("l", 0.0)
634646
k3 = d.get("k3", 0.0)
635647
k3s = d.get("k3s", 0.0)
636-
tilt_degree = d.get("tilt", 0.0) * 180.0 / math.pi
648+
tilt_degree = d.get("tilt", 0.0) * rad_to_deg
637649
if ds > 0:
638650
impactx_beamline.append(
639651
elements.ExactMultipole(
@@ -668,7 +680,7 @@ def append_thin_with_length(thin_element, length, element_name):
668680
ksl = d.get("ksl", [])
669681
ds = d.get("l", 0.0)
670682
lrad = d.get("lrad", 0.0)
671-
tilt_degree = d.get("tilt", 0.0) * 180.0 / math.pi
683+
tilt_degree = d.get("tilt", 0.0) * rad_to_deg
672684
max_order = max(len(knl), len(ksl)) - 1
673685

674686
if ds > 0.0:
@@ -746,7 +758,7 @@ def append_thin_with_length(thin_element, length, element_name):
746758
lag = d.get("lag", 0.0) # fraction of 2pi
747759
harmon = d.get("harmon", 1.0)
748760
phase = lag * 360.0 # convert to degrees
749-
tilt_degree = d.get("tilt", 0.0) * 180.0 / math.pi
761+
tilt_degree = d.get("tilt", 0.0) * rad_to_deg
750762
if "freq" in d and d.get("freq", 0.0) > 0.0:
751763
# MAD-X RFCAVITY frequency is specified in MHz.
752764
freq = d.get("freq", 0.0) * 1.0e6

0 commit comments

Comments
 (0)