1
+ from __future__ import annotations
2
+
3
+ __all__ = ["get_energy_loss" , "set_cavity_phase" , "ELossMethod" , "get_timelag_fromU0" ]
4
+
1
5
from enum import Enum
2
6
from warnings import warn
3
- from math import pi
4
- from typing import Optional , Tuple
5
- import numpy
7
+ from collections . abc import Sequence
8
+
9
+ import numpy as np
6
10
from scipy .optimize import least_squares
11
+
12
+ from at .constants import clight , Cgamma
7
13
from at .lattice import Lattice , Dipole , Wiggler , RFCavity , Refpts , EnergyLoss
8
14
from at .lattice import check_radiation , AtError , AtWarning
9
- from at .lattice import QuantumDiffusion , Collective , SimpleQuantDiff
10
15
from at .lattice import get_bool_index , set_value_refpts
11
- from at .constants import clight , Cgamma
12
- from at .tracking import internal_lpass
13
-
14
- __all__ = ['get_energy_loss' , 'set_cavity_phase' , 'ELossMethod' ,
15
- 'get_timelag_fromU0' ]
16
16
17
17
18
18
class ELossMethod (Enum ):
19
19
"""methods for the computation of energy losses"""
20
+
20
21
#: The losses are obtained from
21
22
#: :math:`E_{loss}=C_\gamma/2\pi . E^4 . I_2`.
22
23
#: Takes into account bending magnets and wigglers.
@@ -26,9 +27,9 @@ class ELossMethod(Enum):
26
27
TRACKING = 2
27
28
28
29
29
- def get_energy_loss (ring : Lattice ,
30
- method : Optional [ ELossMethod ] = ELossMethod .INTEGRAL
31
- ) -> float :
30
+ def get_energy_loss (
31
+ ring : Lattice , method : ELossMethod | None = ELossMethod .INTEGRAL
32
+ ) -> float :
32
33
"""Computes the energy loss per turn
33
34
34
35
Parameters:
@@ -42,153 +43,168 @@ def get_energy_loss(ring: Lattice,
42
43
43
44
# noinspection PyShadowingNames
44
45
def integral (ring ):
45
- """Losses = Cgamma / 2pi * EGeV^4 * i2
46
- """
46
+ """Losses = Cgamma / 2pi * EGeV^4 * i2"""
47
47
48
48
def wiggler_i2 (wiggler : Wiggler ):
49
49
rhoinv = wiggler .Bmax / ring .BRho
50
50
coefh = wiggler .By [1 , :]
51
51
coefv = wiggler .Bx [1 , :]
52
- return wiggler .Length * (numpy .sum (coefh * coefh ) + numpy .sum (
53
- coefv * coefv )) * rhoinv ** 2 / 2
52
+ return (
53
+ wiggler .Length
54
+ * (np .sum (coefh * coefh ) + np .sum (coefv * coefv ))
55
+ * rhoinv ** 2
56
+ / 2
57
+ )
54
58
55
59
def dipole_i2 (dipole : Dipole ):
56
- return dipole .BendingAngle ** 2 / dipole .Length
60
+ return dipole .BendingAngle ** 2 / dipole .Length
57
61
58
62
def eloss_i2 (eloss : EnergyLoss ):
59
63
return eloss .EnergyLoss / coef
60
64
61
65
i2 = 0.0
62
- coef = Cgamma / 2.0 / pi * ring .energy ** 4
66
+ coef = Cgamma / 2.0 / np . pi * ring .energy ** 4
63
67
for el in ring :
64
68
if isinstance (el , Dipole ):
65
69
i2 += dipole_i2 (el )
66
- elif isinstance (el , Wiggler ) and el .PassMethod != ' DriftPass' :
70
+ elif isinstance (el , Wiggler ) and el .PassMethod != " DriftPass" :
67
71
i2 += wiggler_i2 (el )
68
- elif isinstance (el , EnergyLoss ) and el .PassMethod != ' IdentityPass' :
72
+ elif isinstance (el , EnergyLoss ) and el .PassMethod != " IdentityPass" :
69
73
i2 += eloss_i2 (el )
70
74
e_loss = coef * i2
71
75
return e_loss
72
76
73
77
# noinspection PyShadowingNames
74
78
@check_radiation (True )
75
79
def tracking (ring ):
76
- """Losses from tracking
77
- """
78
- ringtmp = ring .disable_6d (RFCavity , QuantumDiffusion , Collective ,
79
- SimpleQuantDiff , copy = True )
80
- o6 = numpy .squeeze (internal_lpass (ringtmp , numpy .zeros (6 ),
81
- refpts = len (ringtmp )))
82
- if numpy .isnan (o6 [0 ]):
83
- dp = 0
84
- for e in ringtmp :
85
- ot = numpy .squeeze (internal_lpass ([e ], numpy .zeros (6 )))
86
- dp += - ot [4 ] * ring .energy
87
- return dp
88
- else :
89
- return - o6 [4 ] * ring .energy
80
+ """Losses from tracking"""
81
+ energy = ring .energy
82
+ particle = ring .particle
83
+ delta = 0.0
84
+ for e in ring :
85
+ if e .PassMethod .endswith ("RadPass" ):
86
+ ot = e .track (np .zeros (6 ), energy = energy , particle = particle )
87
+ delta += ot [4 ]
88
+ return - delta * energy
90
89
91
90
if isinstance (method , str ):
92
91
method = ELossMethod [method .upper ()]
93
- warn (FutureWarning (' You should use {0 !s}' . format ( method )) )
92
+ warn (FutureWarning (f" You should use { method !s} " ), stacklevel = 2 )
94
93
if method is ELossMethod .INTEGRAL :
95
94
return ring .periodicity * integral (ring )
96
95
elif method == ELossMethod .TRACKING :
97
96
return ring .periodicity * tracking (ring )
98
97
else :
99
- raise AtError (' Invalid method: {}' . format ( method ) )
98
+ raise AtError (f" Invalid method: { method } " )
100
99
101
100
102
101
# noinspection PyPep8Naming
103
- def get_timelag_fromU0 (ring : Lattice ,
104
- method : Optional [ELossMethod ] = ELossMethod .TRACKING ,
105
- cavpts : Optional [Refpts ] = None ,
106
- divider : Optional [int ] = 4 ,
107
- ts_tol : Optional [float ] = 1.0e-9 ) -> Tuple [float , float ]:
102
+ def get_timelag_fromU0 (
103
+ ring : Lattice ,
104
+ * ,
105
+ method : ELossMethod | None = ELossMethod .TRACKING ,
106
+ cavpts : Refpts | None = None ,
107
+ divider : int | None = 4 ,
108
+ ts_tol : float | None = 1.0e-9 ,
109
+ ) -> tuple [Sequence [float ], float ]:
108
110
"""
109
111
Get the TimeLag attribute of RF cavities based on frequency,
110
112
voltage and energy loss per turn, so that the synchronous phase is zero.
111
- An error occurs if all cavities do not have the same frequency.
112
113
Used in set_cavity_phase()
113
114
114
-
115
115
Parameters:
116
116
ring: Lattice description
117
117
method: Method for energy loss computation.
118
118
See :py:class:`ELossMethod`.
119
119
cavpts: Cavity location. If None, use all cavities.
120
120
This allows to ignore harmonic cavities.
121
121
divider: number of segments to search for ts
122
- phis_tol : relative tolerance for ts calculation
122
+ ts_tol : relative tolerance for ts calculation
123
123
Returns:
124
- timelag (float): Timelag
124
+ timelag (float): (ncav,) array of * Timelag* values
125
125
ts (float): Time difference with the present value
126
126
"""
127
+
127
128
def singlev (values ):
128
- vals = numpy .unique (values )
129
+ vals = np .unique (values )
129
130
if len (vals ) > 1 :
130
- raise AtError (' values not equal for all cavities' )
131
+ raise AtError (" values not equal for all cavities" )
131
132
return vals [0 ]
132
133
133
134
def eq (x , freq , rfv , tl0 , u0 ):
134
- omf = 2 * numpy .pi * freq / clight
135
+ omf = 2 * np .pi * freq / clight
135
136
if u0 > 0.0 :
136
- eq1 = (numpy .sum (- rfv * numpy .sin (omf * ( x - tl0 )))- u0 )/ u0
137
+ eq1 = (np .sum (- rfv * np .sin (omf * ( x - tl0 ))) - u0 ) / u0
137
138
else :
138
- eq1 = numpy .sum (- rfv * numpy .sin (omf * (x - tl0 )))
139
- eq2 = numpy .sum (- omf * rfv * numpy .cos (omf * ( x - tl0 )))
139
+ eq1 = np .sum (- rfv * np .sin (omf * (x - tl0 )))
140
+ eq2 = np .sum (- omf * rfv * np .cos (omf * ( x - tl0 )))
140
141
if eq2 > 0 :
141
- return numpy .sqrt (eq1 ** 2 + eq2 ** 2 )
142
+ return np .sqrt (eq1 ** 2 + eq2 ** 2 )
142
143
else :
143
144
return abs (eq1 )
144
145
145
146
if cavpts is None :
146
147
cavpts = get_bool_index (ring , RFCavity )
147
148
u0 = get_energy_loss (ring , method = method ) / ring .periodicity
148
- freq = numpy .array ([cav .Frequency for cav in ring .select (cavpts )])
149
- rfv = numpy .array ([cav .Voltage for cav in ring .select (cavpts )])
150
- tl0 = numpy .array ([cav .TimeLag for cav in ring .select (cavpts )])
149
+ freq = np .array ([cav .Frequency for cav in ring .select (cavpts )])
150
+ rfv = np .array ([cav .Voltage for cav in ring .select (cavpts )])
151
+ tl0 = np .array ([cav .TimeLag for cav in ring .select (cavpts )])
151
152
try :
152
153
frf = singlev (freq )
153
154
tml = singlev (tl0 )
154
155
except AtError :
155
- ctmax = clight / numpy .amin (freq )/ 2
156
- tt0 = tl0 [numpy .argmin (freq )]
156
+ ctmax = clight / np .amin (freq ) / 2
157
+ tt0 = tl0 [np .argmin (freq )]
157
158
bounds = (- ctmax , ctmax )
158
159
args = (freq , rfv , tl0 , u0 )
159
160
r = []
160
161
for i in range (divider ):
161
- fact = (i + 1 )/ divider
162
- r .append (least_squares (eq , bounds [0 ]* fact + tt0 ,
163
- args = args , bounds = bounds + tt0 ))
164
- r .append (least_squares (eq , bounds [1 ]* fact + tt0 ,
165
- args = args , bounds = bounds + tt0 ))
166
- res = numpy .array ([ri .fun [0 ] for ri in r ])
162
+ fact = (i + 1 ) / divider
163
+ r .append (
164
+ least_squares (
165
+ eq , bounds [0 ] * fact + tt0 , args = args , bounds = bounds + tt0
166
+ )
167
+ )
168
+ r .append (
169
+ least_squares (
170
+ eq , bounds [1 ] * fact + tt0 , args = args , bounds = bounds + tt0
171
+ )
172
+ )
173
+ res = np .array ([ri .fun [0 ] for ri in r ])
167
174
ok = res < ts_tol
168
- vals = numpy .array ([abs (ri .x [0 ]).round (decimals = 6 ) for ri in r ])
169
- if not numpy .any (ok ):
170
- raise AtError ('No solution found for Phis, please check '
171
- 'RF settings' )
172
- if len (numpy .unique (vals [ok ])) > 1 :
173
- warn (AtWarning ('More than one solution found for Phis: use '
174
- 'best fit, please check RF settings' ))
175
- ts = - r [numpy .argmin (res )].x [0 ]
176
- timelag = ts + tl0
175
+ vals = np .array ([abs (ri .x [0 ]).round (decimals = 6 ) for ri in r ])
176
+ if not np .any (ok ):
177
+ raise AtError ("No solution found for Phis: check RF settings" ) from None
178
+ if len (np .unique (vals [ok ])) > 1 :
179
+ warn (
180
+ AtWarning ("More than one solution found for Phis: check RF settings" ),
181
+ stacklevel = 2 ,
182
+ )
183
+ ts = - r [np .argmin (res )].x [0 ]
184
+ timelag = ts + tl0
177
185
else :
178
- if u0 > numpy .sum (rfv ):
179
- raise AtError ('Not enough RF voltage: unstable ring' )
180
- vrf = numpy .sum (rfv )
181
- timelag = clight / (2 * numpy .pi * frf )* numpy .arcsin (u0 / vrf )
186
+ vrf = np .sum (rfv )
187
+ if u0 > vrf :
188
+ v1 = ring .periodicity * vrf
189
+ v2 = ring .periodicity * u0
190
+ raise AtError (
191
+ f"The RF voltage ({ v1 :.3e} eV) is lower than "
192
+ f"the radiation losses ({ v2 :.3e} eV)."
193
+ )
194
+ timelag = clight / (2 * np .pi * frf ) * np .arcsin (u0 / vrf )
182
195
ts = timelag - tml
183
- timelag *= numpy .ones (ring .refcount (cavpts ))
196
+ timelag *= np .ones (ring .refcount (cavpts ))
184
197
return timelag , ts
185
198
186
199
187
- def set_cavity_phase (ring : Lattice ,
188
- method : ELossMethod = ELossMethod .TRACKING ,
189
- refpts : Optional [Refpts ] = None ,
190
- cavpts : Optional [Refpts ] = None ,
191
- copy : bool = False ) -> None :
200
+ def set_cavity_phase (
201
+ ring : Lattice ,
202
+ * ,
203
+ method : ELossMethod = ELossMethod .TRACKING ,
204
+ refpts : Refpts | None = None ,
205
+ cavpts : Refpts | None = None ,
206
+ copy : bool = False ,
207
+ ) -> None :
192
208
"""
193
209
Adjust the TimeLag attribute of RF cavities based on frequency,
194
210
voltage and energy loss per turn, so that the synchronous phase is zero.
@@ -209,12 +225,12 @@ def set_cavity_phase(ring: Lattice,
209
225
"""
210
226
# refpts is kept for backward compatibility
211
227
if cavpts is None and refpts is not None :
212
- warn (FutureWarning ('You should use "cavpts" instead of "refpts"' ))
228
+ warn (FutureWarning ('You should use "cavpts" instead of "refpts"' ), stacklevel = 2 )
213
229
cavpts = refpts
214
230
elif cavpts is None :
215
231
cavpts = get_bool_index (ring , RFCavity )
216
232
timelag , _ = get_timelag_fromU0 (ring , method = method , cavpts = cavpts )
217
- set_value_refpts (ring , cavpts , ' TimeLag' , timelag , copy = copy )
233
+ set_value_refpts (ring , cavpts , " TimeLag" , timelag , copy = copy )
218
234
219
235
220
236
Lattice .get_energy_loss = get_energy_loss
0 commit comments