@@ -107,3 +107,192 @@ def test_madx_import():
107107 )
108108 else :
109109 raise ValueError ("mode not understood" )
110+
111+
112+ def test_error_import ():
113+ cpymad_spec = util .find_spec ("cpymad" )
114+ if cpymad_spec is None :
115+ print ("cpymad is not available - abort test" )
116+ sys .exit (0 )
117+
118+ from cpymad .madx import Madx
119+
120+ madx = Madx ()
121+
122+ madx .input ('''
123+ MQ1: Quadrupole, K1:=KQ1, L=1.0, apertype=CIRCLE, aperture={0.04};
124+ MQ2: Quadrupole, K1:=KQ2, L=1.0, apertype=CIRCLE, aperture={0.04};
125+ MQ3: Quadrupole, K1:=0.0, L=1.0, apertype=CIRCLE, aperture={0.04};
126+
127+ KQ1 = 0.02;
128+ KQ2 = -0.02;
129+
130+ testseq: SEQUENCE, l = 20.0;
131+ MQ1, at = 5;
132+ MQ2, at = 12;
133+ MQ3, at = 18;
134+ ENDSEQUENCE;
135+
136+ !---the usual stuff
137+ BEAM, PARTICLE=PROTON, ENERGY=7000.0, EXN=2.2e-6, EYN=2.2e-6;
138+ USE, SEQUENCE=testseq;
139+
140+
141+ Select, flag=makethin, pattern="MQ1", slice=2;
142+ makethin, sequence=testseq;
143+
144+ use, sequence=testseq;
145+
146+ !---assign misalignments and field errors
147+ select, flag = error, clear;
148+ select, flag = error, pattern = "MQ1";
149+ ealign, dx = 0.01, dy = 0.01, arex = 0.02, arey = 0.02;
150+ select, flag = error, clear;
151+ select, flag = error, pattern = "MQ2";
152+ ealign, dx = 0.04, dy = 0.04, dpsi = 0.1;
153+ select, flag = error, clear;
154+ select, flag = error, pattern = "MQ3";
155+ ealign, dx = 0.00, dy = 0.00, arex = 0.00, arey = 0.00, dpsi = 0.00;
156+ efcomp, DKN = {0.0, 0.0, 0.001, 0.002}, DKS = {0.0, 0.0, 0.003, 0.004};
157+ select, flag = error, full;
158+ ''' )
159+ seq = madx .sequence .testseq
160+ # store already applied errors:
161+ madx .command .esave (file = 'lattice_errors.err' )
162+ madx .command .readtable (file = 'lattice_errors.err' , table = "errors" )
163+ os .remove ('lattice_errors.err' )
164+ errors = madx .table .errors
165+
166+ pysixtrack_line = pysixtrack .Line .from_madx_sequence (seq , install_apertures = True )
167+ pysixtrack_line .apply_madx_errors (errors )
168+ madx .input ('stop;' )
169+
170+ expected_element_num = (
171+ 2 # start and end marker
172+ + 6 # drifts (including drift between MQ1 slices)
173+ + 3 + 2 # quadrupoles + MQ1 slices
174+ + 3 + 2 # corresponding aperture elements
175+ + 2 * (3 + 1 ) # dx/y in/out for MQ1 slices and MQ2
176+ + 2 # tilt in/out for MQ2
177+ + 2 * 3 # arex/y in/out for MQ1 slices
178+ )
179+ assert len (pysixtrack_line ) == expected_element_num
180+
181+ expected_element_order = [
182+ pysixtrack .elements .Drift , # start marker
183+ pysixtrack .elements .Drift ,
184+ pysixtrack .elements .XYShift , # dx/y in of MQ1 1st slice
185+ pysixtrack .elements .Multipole , # MQ1 1st slice
186+ pysixtrack .elements .XYShift , # arex/y in for MQ1 1st slice
187+ pysixtrack .elements .LimitEllipse , # MQ1 1st slice aperture
188+ pysixtrack .elements .XYShift , # arex/y out for MQ1 1st slice
189+ pysixtrack .elements .XYShift , # dx/y out for MQ1 1st slice
190+ pysixtrack .elements .Drift ,
191+ pysixtrack .elements .XYShift , # dx/y in for MQ1 marker
192+ pysixtrack .elements .Drift , # MQ1 marker
193+ pysixtrack .elements .XYShift , # arex/y in for MQ1 marker
194+ pysixtrack .elements .LimitEllipse , # MQ1 marker aperture
195+ pysixtrack .elements .XYShift , # arex/y out for MQ1 marker
196+ pysixtrack .elements .XYShift , # dx/y out for MQ1 marker
197+ pysixtrack .elements .Drift ,
198+ pysixtrack .elements .XYShift , # dx/y in for MQ1 2nd slice
199+ pysixtrack .elements .Multipole , # MQ1 2nd slice
200+ pysixtrack .elements .XYShift , # arex/y in for MQ1 2nd slice
201+ pysixtrack .elements .LimitEllipse , # MQ1 2nd slice aperture
202+ pysixtrack .elements .XYShift , # arex/y out for MQ1 2nd slice
203+ pysixtrack .elements .XYShift , # dx/y out for MQ1 2nd slice
204+ pysixtrack .elements .Drift ,
205+ pysixtrack .elements .XYShift , # dx/y in for MQ2
206+ pysixtrack .elements .SRotation , # tilt in for MQ2
207+ pysixtrack .elements .Multipole , # MQ2
208+ pysixtrack .elements .LimitEllipse , # MQ2 aperture
209+ pysixtrack .elements .SRotation , # tilt out for MQ2
210+ pysixtrack .elements .XYShift , # dx/y out for MQ2
211+ pysixtrack .elements .Drift ,
212+ pysixtrack .elements .Multipole , # MQ3
213+ pysixtrack .elements .LimitEllipse , # MQ3 aperture
214+ pysixtrack .elements .Drift ,
215+ pysixtrack .elements .Drift # end marker
216+ ]
217+ for element , expected_element in zip (pysixtrack_line .elements ,
218+ expected_element_order ):
219+ assert isinstance (element , expected_element )
220+
221+ idx_MQ3 = pysixtrack_line .element_names .index ('mq3' )
222+ MQ3 = pysixtrack_line .elements [idx_MQ3 ]
223+ assert abs (MQ3 .knl [2 ] - 0.001 ) < 1e-14
224+ assert abs (MQ3 .knl [3 ] - 0.002 ) < 1e-14
225+ assert abs (MQ3 .ksl [2 ] - 0.003 ) < 1e-14
226+ assert abs (MQ3 .ksl [3 ] - 0.004 ) < 1e-14
227+
228+
229+ def test_neutral_errors ():
230+ # make sure that some misaligned drifts do not influence particle
231+ cpymad_spec = util .find_spec ("cpymad" )
232+ if cpymad_spec is None :
233+ print ("cpymad is not available - abort test" )
234+ sys .exit (0 )
235+
236+ from cpymad .madx import Madx
237+
238+ madx = Madx ()
239+
240+ madx .input ('''
241+ T1: Collimator, L=1.0, apertype=CIRCLE, aperture={0.5};
242+ T2: Collimator, L=1.0, apertype=CIRCLE, aperture={0.5};
243+ T3: Collimator, L=1.0, apertype=CIRCLE, aperture={0.5};
244+
245+ KQ1 = 0.02;
246+ KQ2 = -0.02;
247+
248+ testseq: SEQUENCE, l = 20.0;
249+ T1, at = 5;
250+ T2, at = 12;
251+ T3, at = 18;
252+ ENDSEQUENCE;
253+
254+ !---the usual stuff
255+ BEAM, PARTICLE=PROTON, ENERGY=7000.0, EXN=2.2e-6, EYN=2.2e-6;
256+ USE, SEQUENCE=testseq;
257+
258+
259+ Select, flag=makethin, pattern="MQ1", slice=2;
260+ makethin, sequence=testseq;
261+
262+ use, sequence=testseq;
263+
264+ !---misalign collimators
265+ select, flag = error, clear;
266+ select, flag = error, pattern = "T1";
267+ ealign, dx = 0.01, dy = 0.01, arex = 0.02, arey = 0.02;
268+ select, flag = error, clear;
269+ select, flag = error, pattern = "T2";
270+ ealign, dx = 0.04, dy = 0.04, dpsi = 0.1;
271+ select, flag = error, clear;
272+ select, flag = error, pattern = "T3";
273+ ealign, dx = 0.02, dy = 0.01, arex = 0.03, arey = 0.02, dpsi = 0.1;
274+ select, flag = error, full;
275+ ''' )
276+ seq = madx .sequence .testseq
277+ # store already applied errors:
278+ madx .command .esave (file = 'lattice_errors.err' )
279+ madx .command .readtable (file = 'lattice_errors.err' , table = "errors" )
280+ os .remove ('lattice_errors.err' )
281+ errors = madx .table .errors
282+
283+ pysixtrack_line = pysixtrack .Line .from_madx_sequence (seq , install_apertures = True )
284+ pysixtrack_line .apply_madx_errors (errors )
285+ madx .input ('stop;' )
286+
287+ initial_x = 0.025
288+ initial_y = - 0.015
289+
290+ particle = pysixtrack .Particles ()
291+ particle .x = initial_x
292+ particle .y = initial_y
293+ particle .state = 1
294+
295+ pysixtrack_line .track (particle )
296+
297+ assert abs (particle .x - initial_x ) < 1e-14
298+ assert abs (particle .y - initial_y ) < 1e-14
0 commit comments