22
33import logging
44from dataclasses import dataclass
5+ from enum import StrEnum , auto
56from warnings import warn
67
78import at
1213from scipy .constants import speed_of_light
1314
1415
16+ class LinoptType (StrEnum ):
17+ LINOPT2 = auto ()
18+ LINOPT4 = auto ()
19+ LINOPT6 = auto ()
20+
21+
22+ @dataclass (frozen = True )
23+ class SimParams :
24+ linopt : LinoptType = LinoptType .LINOPT6
25+ emittance : bool = True
26+ chromaticity : bool = True
27+ radiation : bool = True
28+
29+ def __post_init__ (self ):
30+ """Check that we have a valid combination of simulation parameters."""
31+ try :
32+ LinoptType (self .linopt )
33+ except ValueError as e :
34+ raise ValueError (
35+ f"{ self .linopt } is not a valid linopt function. Choose from: "
36+ f"{ [sp .value for sp in LinoptType ]} "
37+ ) from e
38+
39+ if self .linopt == LinoptType .LINOPT2 or self .linopt == LinoptType .LINOPT4 :
40+ if self .emittance or self .radiation :
41+ raise ValueError (
42+ "Emittance and radiation calculations must be disabled when using "
43+ f"{ self .linopt } " ,
44+ )
45+ elif self .linopt == LinoptType .LINOPT6 :
46+ if not self .radiation and self .emittance :
47+ raise ValueError (
48+ "You cannot calculate emittance with radiation disabled" ,
49+ )
50+
51+
1552@dataclass
1653class LatticeData :
1754 twiss : ArrayLike
@@ -22,9 +59,7 @@ class LatticeData:
2259
2360
2461def calculate_optics (
25- at_lattice : at .lattice_object .Lattice ,
26- refpts : ArrayLike ,
27- disable_emittance : bool = False ,
62+ at_lattice : at .lattice_object .Lattice , refpts : ArrayLike , sp : SimParams
2863) -> LatticeData :
2964 """Perform the physics calculations on the lattice.
3065
@@ -36,28 +71,55 @@ def calculate_optics(
3671 Args:
3772 at_lattice (at.lattice_object.Lattice): AT lattice definition.
3873 refpts (numpy.typing.NDArray): A boolean array specifying the points at which
39- to calculate physics data.
40- disable_emittance (bool): whether to calculate emittance.
74+ to calculate physics data.
75+ sp (SimParams): An optional dataclass containing the pyAT simulation
76+ parameters to use.
4177
4278 Returns:
4379 LatticeData: The calculated lattice data.
4480 """
4581 logging .debug ("Starting physics calculations." )
82+ logging .debug (
83+ f"Using simulation params: { sp .linopt } , emittance={ sp .emittance } , chromaticity="
84+ f"{ sp .chromaticity } , radiation={ sp .radiation } "
85+ )
4686
47- orbit0 , _ = at_lattice .find_orbit6 ()
87+ match sp .linopt :
88+ case LinoptType .LINOPT2 :
89+ orbit_func = at_lattice .find_orbit
90+ linopt_func = at_lattice .linopt2
91+ case LinoptType .LINOPT4 :
92+ orbit_func = at_lattice .find_orbit4
93+ linopt_func = at_lattice .linopt4
94+ case LinoptType .LINOPT6 :
95+ orbit_func = at_lattice .find_orbit6
96+ linopt_func = at_lattice .linopt6
97+
98+ # Perform pyAT orbit calculation
99+ orbit0 , _ = orbit_func ()
48100 logging .debug ("Completed orbit calculation." )
49101
50- _ , beamdata , twiss = at_lattice .linopt6 (
51- refpts = refpts , get_chrom = True , orbit = orbit0 , keep_lattice = True
102+ # Perform pyAT linear optics calculation
103+ _ , beamdata , twiss = linopt_func (
104+ refpts = refpts ,
105+ get_chrom = sp .chromaticity ,
106+ orbit = orbit0 ,
107+ keep_lattice = True ,
52108 )
53109 logging .debug ("Completed linear optics calculation." )
54110
55- if not disable_emittance :
111+ if sp . emittance :
56112 emitdata = at_lattice .ohmi_envelope (orbit = orbit0 , keep_lattice = True )
57113 logging .debug ("Completed emittance calculation" )
58114 else :
59115 emitdata = ()
60- radint = at_lattice .get_radiation_integrals (twiss = twiss )
116+
117+ if sp .radiation :
118+ radint = at_lattice .get_radiation_integrals (twiss = twiss )
119+ logging .debug ("Completed radiation calculation" )
120+ else :
121+ radint = ()
122+
61123 logging .debug ("All calculation complete." )
62124 return LatticeData (twiss , beamdata .tune , beamdata .chromaticity , emitdata , radint )
63125
@@ -83,8 +145,6 @@ class ATSimulator:
83145 physics data is calculated.
84146 _rp (numpy.typing.NDArray): A boolean array to be used as refpts for the
85147 physics calculations.
86- _disable_emittance (bool): Whether or not to perform the beam
87- envelope based emittance calculations.
88148 _lattice_data (LatticeData): calculated physics data
89149 function linopt (see at.lattice.linear.py).
90150 _queue (cothread.EventQueue): A queue of changes to be applied to
@@ -98,7 +158,12 @@ class ATSimulator:
98158 physics data upon a change.
99159 """
100160
101- def __init__ (self , at_lattice , callback = None , disable_emittance = False ):
161+ def __init__ (
162+ self ,
163+ at_lattice ,
164+ sim_params = None ,
165+ callback = None ,
166+ ):
102167 """
103168 .. Note:: To avoid errors, the physics data must be initially
104169 calculated here, during creation, otherwise it could be accidentally
@@ -107,12 +172,11 @@ def __init__(self, at_lattice, callback=None, disable_emittance=False):
107172 the thread.
108173
109174 Args:
110- at_lattice (at.lattice_object.Lattice): An instance of an AT
111- lattice object.
112- callback (typing.Callable): Optional, if passed it is called on completion
113- of each round of physics calculations.
114- disable_emittance (bool): Whether or not to perform the beam
115- envelope based emittance calculations.
175+ at_lattice (at.lattice_object.Lattice): An instance of an AT lattice object.
176+ sim_params (SimParams | None): An optional dataclass containing the pyAT
177+ simulation parameters to use.
178+ callback (typing.Callable): To be called after completion of each round of
179+ physics calculations.
116180
117181 **Methods:**
118182 """
@@ -122,13 +186,16 @@ def __init__(self, at_lattice, callback=None, disable_emittance=False):
122186 )
123187 self ._at_lat = at_lattice
124188 self ._rp = numpy .ones (len (at_lattice ) + 1 , dtype = bool )
125- self ._disable_emittance = disable_emittance
126- self ._at_lat .radiation_on ()
189+
190+ if sim_params is None :
191+ sim_params = SimParams ()
192+ self ._sim_params = sim_params
193+
194+ if self ._sim_params .linopt == LinoptType .LINOPT6 :
195+ self ._at_lat .enable_6d ()
127196
128197 # Initial phys data calculation.
129- self ._lattice_data = calculate_optics (
130- self ._at_lat , self ._rp , self ._disable_emittance
131- )
198+ self ._lattice_data = calculate_optics (self ._at_lat , self ._rp , self ._sim_params )
132199
133200 # Threading stuff initialisation.
134201 self ._queue = cothread .EventQueue ()
@@ -196,7 +263,7 @@ def _recalculate_phys_data(self, callback):
196263 if bool (self ._paused ) is False :
197264 try :
198265 self ._lattice_data = calculate_optics (
199- self ._at_lat , self ._rp , self ._disable_emittance
266+ self ._at_lat , self ._rp , self ._sim_params
200267 )
201268 except Exception as e :
202269 warn (at .AtWarning (e ), stacklevel = 1 )
@@ -493,7 +560,7 @@ def get_emittance(self, field=None):
493560 Raises:
494561 pytac.FieldException: if the specified field is not valid for emittance.
495562 """
496- if not self ._disable_emittance :
563+ if self ._sim_params . emittance :
497564 if field is None :
498565 return self ._lattice_data .emittance [0 ]["emitXY" ]
499566 elif field == "x" :
0 commit comments