Skip to content

Commit 5f6c819

Browse files
committed
Allow the output printing of markers
1 parent 5988a5d commit 5f6c819

File tree

8 files changed

+261
-13
lines changed

8 files changed

+261
-13
lines changed

sad2xs/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ class Config:
148148
default_factory = lambda: {
149149
"Marker", "Drift",
150150
"Bend", "Quadrupole", "Sextupole", "Octupole", "Multipole", "Solenoid",
151-
"Cavity", "XYShift", "ZetaShift", "XRotation", "YRotation", "SRotation"})
151+
"Cavity", "XYShift", "ZetaShift", "XRotation", "YRotation", "SRotation",
152+
"LimitEllipse", "LimitRect"})
152153

153154
########################################
154155
# Marker Insertion Tolerance

sad2xs/converter/_004_element_converter.py

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
=============================================
44
Author(s): John P T Salvesen
55
6-
Date: 09-10-2025
6+
Date: 18-11-2025
77
"""
88

99
################################################################################
@@ -1300,25 +1300,132 @@ def convert_apertures(parsed_elements, environment):
13001300
########################################
13011301
# Initialise parameters
13021302
########################################
1303-
a = 1.0
1304-
b = 1.0
1303+
offset_x = 0.0
1304+
offset_y = 0.0
1305+
a = None
1306+
b = None
1307+
dx1 = None
1308+
dx2 = None
1309+
dy1 = None
1310+
dy2 = None
1311+
aper_type = None
13051312

13061313
########################################
13071314
# Read values
13081315
########################################
1316+
if 'dx' in ele_vars:
1317+
offset_x = parse_expression(ele_vars['dx'])
1318+
if 'dy' in ele_vars:
1319+
offset_y = parse_expression(ele_vars['dy'])
13091320
if 'ax' in ele_vars:
13101321
a = parse_expression(ele_vars['ax'])
13111322
if 'ay' in ele_vars:
13121323
b = parse_expression(ele_vars['ay'])
1324+
if "dx1" in ele_vars:
1325+
dx1 = parse_expression(ele_vars['dx1'])
1326+
if "dx2" in ele_vars:
1327+
dx2 = parse_expression(ele_vars['dx2'])
1328+
if "dy1" in ele_vars:
1329+
dy1 = parse_expression(ele_vars['dy1'])
1330+
if "dy2" in ele_vars:
1331+
dy2 = parse_expression(ele_vars['dy2'])
1332+
1333+
########################################
1334+
# Determine type of aperture
1335+
########################################
1336+
if any(v is not None for v in [dx1, dx2, dy1, dy2]) and \
1337+
any(v is not None for v in [a, b]):
1338+
raise ValueError(
1339+
f"Error! Aperture {ele_name} has both rectangular and elliptical definitions. This is not supported.")
1340+
elif any(v is not None for v in [dx1, dx2, dy1, dy2]):
1341+
aper_type = 'LimitRect'
1342+
1343+
if dx1 is None and dx2 is None:
1344+
dx1 = -1.0
1345+
dx2 = +1.0
1346+
elif dx1 is None and isinstance(dx2, float):
1347+
if dx2 < 0:
1348+
dx1 = dx2
1349+
dx2 = +1.0
1350+
else:
1351+
dx1 = -1.0
1352+
elif dx2 is None and isinstance(dx1, float):
1353+
if dx1 < 0:
1354+
dx2 = +1.0
1355+
else:
1356+
dx2 = dx1
1357+
dx1 = -1.0
1358+
elif isinstance(dx1, float) and isinstance(dx2, float):
1359+
if dx1 > dx2:
1360+
tmp = dx1
1361+
dx1 = dx2
1362+
dx2 = tmp
1363+
else:
1364+
# At least one is expression, cannot compare
1365+
# This might cause issues
1366+
pass
1367+
1368+
if dy1 is None and dy2 is None:
1369+
dy1 = -1.0
1370+
dy2 = +1.0
1371+
elif dy1 is None and isinstance(dy2, float):
1372+
if dy2 < 0:
1373+
dy1 = dy2
1374+
dy2 = +1.0
1375+
else:
1376+
dy1 = -1.0
1377+
elif dy2 is None and isinstance(dy1, float):
1378+
if dy1 < 0:
1379+
dy2 = +1.0
1380+
else:
1381+
dy2 = dy1
1382+
dy1 = -1.0
1383+
elif isinstance(dy1, float) and isinstance(dy2, float):
1384+
if dy1 > dy2:
1385+
tmp = dy1
1386+
dy1 = dy2
1387+
dy2 = tmp
1388+
else:
1389+
# At least one is expression, cannot compare
1390+
# This might cause issues
1391+
pass
1392+
1393+
1394+
elif any(v is not None for v in [a, b]):
1395+
aper_type = 'LimitEllipse'
1396+
1397+
if a is None:
1398+
a = 1.0
1399+
if b is None:
1400+
b = 1.0
1401+
1402+
else:
1403+
raise ValueError(f"Error! Aperture {ele_name} has no valid definition.")
13131404

13141405
########################################
13151406
# Create Element
13161407
########################################
1317-
environment.new(
1318-
name = ele_name,
1319-
parent = xt.LimitEllipse,
1320-
a = a,
1321-
b = b)
1408+
if aper_type == 'LimitRect':
1409+
environment.new(
1410+
name = ele_name,
1411+
parent = xt.LimitRect,
1412+
min_x = dx1,
1413+
max_x = dx2,
1414+
min_y = dy1,
1415+
max_y = dy2,
1416+
shift_x = offset_x,
1417+
shift_y = offset_y)
1418+
continue
1419+
elif aper_type == 'LimitEllipse':
1420+
environment.new(
1421+
name = ele_name,
1422+
parent = xt.LimitEllipse,
1423+
a = a,
1424+
b = b,
1425+
shift_x = offset_x,
1426+
shift_y = offset_y)
1427+
else:
1428+
raise ValueError(f"Error! Aperture {ele_name} has unsupported definition.")
13221429
continue
13231430

13241431
################################################################################

sad2xs/converter/_010_write_lattice.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@
2424
from ..output_writer._008_sol import create_solenoid_lattice_file_information
2525
from ..output_writer._009_cavity import create_cavity_lattice_file_information
2626
from ..output_writer._010_refshift import create_refshift_lattice_file_information
27-
from ..output_writer._011_marker import create_marker_lattice_file_information
28-
from ..output_writer._012_line import create_line_lattice_file_information
29-
from ..output_writer._013_model import create_model_lattice_file_information
30-
from ..output_writer._014_offset_markers import create_offset_marker_lattice_file_information
27+
from ..output_writer._011_aperture import create_aperture_lattice_file_information
28+
from ..output_writer._012_marker import create_marker_lattice_file_information
29+
from ..output_writer._013_line import create_line_lattice_file_information
30+
from ..output_writer._014_model import create_model_lattice_file_information
31+
from ..output_writer._015_offset_markers import create_offset_marker_lattice_file_information
3132

3233
today = date.today()
3334

@@ -228,6 +229,14 @@ def write_lattice(
228229
line = line,
229230
line_table = line_table,
230231
config = config)
232+
233+
########################################
234+
# Apertures
235+
########################################
236+
lattice_file_string += create_aperture_lattice_file_information(
237+
line = line,
238+
line_table = line_table,
239+
config = config)
231240

232241
########################################
233242
# Markers
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""
2+
(Unofficial) SAD to XSuite Converter: Output Writer - Reference Shifts
3+
=============================================
4+
Author(s): John P T Salvesen
5+
6+
Date: 09-10-2025
7+
"""
8+
9+
################################################################################
10+
# Import Packages
11+
################################################################################
12+
import xtrack as xt
13+
import xdeps as xd
14+
import textwrap
15+
16+
from ._000_helpers import *
17+
from ..types import ConfigLike
18+
19+
################################################################################
20+
# Lattice File
21+
################################################################################
22+
def create_aperture_lattice_file_information(
23+
line: xt.Line,
24+
line_table: xd.table.Table,
25+
config: ConfigLike) -> str:
26+
27+
########################################
28+
# Get information
29+
########################################
30+
unique_limitellipse_names = []
31+
unique_limitrect_names = []
32+
33+
for limitellipse in line_table.rows[line_table.element_type == 'LimitEllipse'].name:
34+
parentname = get_parentname(limitellipse)
35+
if parentname not in unique_limitellipse_names:
36+
unique_limitellipse_names.append(parentname)
37+
38+
for limitrect in line_table.rows[line_table.element_type == 'LimitRect'].name:
39+
parentname = get_parentname(limitrect)
40+
if parentname not in unique_limitrect_names:
41+
unique_limitrect_names.append(parentname)
42+
43+
########################################
44+
# Ensure there are reference shifts in the line
45+
########################################
46+
if len(unique_limitellipse_names) == 0 and \
47+
len(unique_limitrect_names) == 0:
48+
return ""
49+
50+
########################################
51+
# Create Output string
52+
########################################
53+
output_string = f"""
54+
############################################################
55+
# Apertures
56+
############################################################
57+
"""
58+
59+
########################################
60+
# Limit Ellipses
61+
########################################
62+
if len(unique_limitellipse_names) != 0:
63+
output_string += f"""
64+
########################################
65+
# Limit Ellipses
66+
########################################"""
67+
68+
for limitellipse_name in unique_limitellipse_names:
69+
70+
# Get the replica information
71+
a = line[limitellipse_name].a
72+
b = line[limitellipse_name].b
73+
shift_x = line[limitellipse_name].shift_x
74+
shift_y = line[limitellipse_name].shift_y
75+
76+
# Remove the minus sign if no non minus version exists
77+
if limitellipse_name.startswith("-"):
78+
root_name = limitellipse_name[1:]
79+
if root_name not in unique_limitellipse_names:
80+
limitellipse_name = root_name
81+
82+
output_string += f"""
83+
env.new(
84+
name = '{limitellipse_name}',
85+
parent = xt.LimitEllipse,
86+
a = {a},
87+
b = {b},
88+
shift_x = {shift_x},
89+
shift_y = {shift_y})"""
90+
91+
output_string += "\n"
92+
93+
########################################
94+
# Limit Rects
95+
########################################
96+
if len(unique_limitrect_names) != 0:
97+
output_string += f"""
98+
########################################
99+
# Limit Rects
100+
########################################"""
101+
102+
for limitrect_name in unique_limitrect_names:
103+
104+
# Get the replica information
105+
min_x = line[limitrect_name].min_x
106+
max_x = line[limitrect_name].max_x
107+
min_y = line[limitrect_name].min_y
108+
max_y = line[limitrect_name].max_y
109+
shift_x = line[limitrect_name].shift_x
110+
shift_y = line[limitrect_name].shift_y
111+
112+
# Remove the minus sign if no non minus version exists
113+
if limitrect_name.startswith("-"):
114+
root_name = limitrect_name[1:]
115+
if root_name not in unique_limitrect_names:
116+
limitrect_name = root_name
117+
118+
output_string += f"""
119+
env.new(
120+
name = '{limitrect_name}',
121+
parent = xt.LimitRect,
122+
min_x = {min_x},
123+
max_x = {max_x},
124+
min_y = {min_y},
125+
max_y = {max_y},
126+
shift_x = {shift_x},
127+
shift_y = {shift_y})"""
128+
129+
output_string += "\n"
130+
131+
return output_string
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)