|
19 | 19 | from scipy.constants import e, c |
20 | 20 |
|
21 | 21 |
|
22 | | -def generate_Gaussian6DTwiss(macroparticlenumber, intensity, charge, mass, |
23 | | - circumference, gamma, |
24 | | - alpha_x, alpha_y, beta_x, beta_y, beta_z, |
25 | | - epsn_x, epsn_y, epsn_z, |
26 | | - dispersion_x=None, dispersion_y=None): |
27 | | - """ Convenience wrapper which generates a 6D gaussian phase space |
28 | | - with the specified parameters |
| 22 | +def generate_Gaussian6DTwiss( |
| 23 | + macroparticlenumber, intensity, charge, mass, |
| 24 | + circumference, gamma, |
| 25 | + alpha_x, alpha_y, beta_x, beta_y, beta_z, |
| 26 | + epsn_x, epsn_y, epsn_z, |
| 27 | + dispersion_x=None, dispersion_y=None, |
| 28 | + limit_n_rms_x=None, limit_n_rms_y=None, limit_n_rms_z=None, |
| 29 | + ): |
| 30 | + """ Convenience wrapper generating a 6D Gaussian phase space |
| 31 | + distribution of macro-particles with the specified parameters: |
| 32 | +
|
29 | 33 | Args: |
30 | | - the usual suspects |
31 | | - Returns: A particle instance with the phase space matched to the arguments |
| 34 | + macroparticlenumber: number of macro-particles in the beam |
| 35 | + intensity: number of represented beam particles |
| 36 | + charge: charge per particle [SI unit Coul] |
| 37 | + mass: mass per particle [SI unit kg] |
| 38 | + circumference: ring circumference (needed for effective models) |
| 39 | + gamma: relativistic Lorentz factor |
| 40 | + alpha_[x,y]: Twiss parameter. The corresponding transverse phase |
| 41 | + space gets matched to (alpha_[], beta_[]) |
| 42 | + beta_[x,y]: Twiss parameter. The corresponding transverse phase |
| 43 | + space gets matched to (alpha_[], beta_[]) |
| 44 | + beta_z: corresponding longitudinal Twiss parameter |
| 45 | + amounting to |eta| * circumference / (2 * pi * Qs) |
| 46 | + epsn_x: horizontal normalised RMS emittance [m.rad] |
| 47 | + epsn_y: vertical normalised RMS emittance [m.rad] |
| 48 | + epsn_z: longitudinal 90% emittance (4x the RMS emittance) [eV.s] |
| 49 | +
|
| 50 | + Optional args: |
| 51 | + dispersion_x: horizontal optics dispersion value for matching |
| 52 | + dispersion_y: vertical optics dispersion value for matching |
| 53 | + limit_n_rms_[x,y]: number of RMS amplitudes to cut distribution |
| 54 | + limit_n_rms_z: longitudinal number of RMS amplitudes to cut |
| 55 | + distribution (remember that epsn_z is already 4x the RMS |
| 56 | + value, i.e. 2 amplitudes) |
| 57 | +
|
| 58 | + Return a Particles instance with the phase space matched to the |
| 59 | + arguments. |
32 | 60 | """ |
33 | | - beta = np.sqrt(1.-gamma**(-2)) |
| 61 | + |
| 62 | + beta = np.sqrt(1. - gamma**-2) |
34 | 63 | p0 = np.sqrt(gamma**2 - 1) * mass * c |
35 | | - eps_geo_x = epsn_x/(beta*gamma) |
36 | | - eps_geo_y = epsn_y/(beta*gamma) |
| 64 | + eps_geo_x = epsn_x / (beta * gamma) |
| 65 | + eps_geo_y = epsn_y / (beta * gamma) |
37 | 66 | eps_geo_z = epsn_z * e / (4. * np.pi * p0) |
| 67 | + |
38 | 68 | # a bit of a hack: epsn_z is a parameter even though the ParticleGenerator |
39 | 69 | # does not have such a parameter. This is kept for backwards compatiblity. |
40 | 70 | # Therefore, some fake eta, Qs parameters are invented s.t. |
41 | 71 | # beta_z = |eta| * circumference / (2 * pi * Qs) |
42 | 72 | # holds (circumference is fixed). Does not have any side effects. |
43 | | - Qs = 1./(2 * np.pi) |
| 73 | + Qs = 1. / (2 * np.pi) |
44 | 74 | eta = beta_z / circumference |
| 75 | + |
| 76 | + distribution_x = gaussian2D(eps_geo_x) |
| 77 | + distribution_y = gaussian2D(eps_geo_y) |
| 78 | + distribution_z = gaussian2D(eps_geo_z) |
| 79 | + # cutting distributions: |
| 80 | + if limit_n_rms_x: |
| 81 | + distribution_x = cut_distribution( |
| 82 | + distribution=distribution_x, |
| 83 | + is_accepted=make_is_accepted_within_n_sigma( |
| 84 | + eps_geo_x, limit_n_rms_x) |
| 85 | + ) |
| 86 | + if limit_n_rms_y: |
| 87 | + distribution_y = cut_distribution( |
| 88 | + distribution=distribution_y, |
| 89 | + is_accepted=make_is_accepted_within_n_sigma( |
| 90 | + eps_geo_y, limit_n_rms_y) |
| 91 | + ) |
| 92 | + if limit_n_rms_z: |
| 93 | + distribution_z = cut_distribution( |
| 94 | + distribution=distribution_z, |
| 95 | + is_accepted=make_is_accepted_within_n_sigma( |
| 96 | + eps_geo_z, limit_n_rms_z) |
| 97 | + ) |
45 | 98 | return ParticleGenerator( |
46 | 99 | macroparticlenumber, intensity, charge, mass, circumference, gamma, |
47 | | - gaussian2D(eps_geo_x), alpha_x, beta_x, dispersion_x, |
48 | | - gaussian2D(eps_geo_y), alpha_y, beta_y, dispersion_y, |
49 | | - gaussian2D(eps_geo_z), Qs, eta |
| 100 | + distribution_x, alpha_x, beta_x, dispersion_x, |
| 101 | + distribution_y, alpha_y, beta_y, dispersion_y, |
| 102 | + distribution_z, Qs, eta |
50 | 103 | ).generate() |
51 | 104 |
|
52 | 105 |
|
@@ -205,30 +258,41 @@ def _cut_distribution(n_particles): |
205 | 258 | return [z, dp] |
206 | 259 | return _cut_distribution |
207 | 260 |
|
208 | | -def make_is_accepted_within_n_sigma(epsn_rms, limit_n_rms, twiss_beta=1): |
| 261 | +def make_is_accepted_within_n_sigma(rms_amplitude=None, limit_n_rms=None, |
| 262 | + epsn_rms=None): |
209 | 263 | '''Closure creating an is_accepted function (e.g. for |
210 | 264 | cut_distribution). The is_accepted function will return whether |
211 | 265 | the canonical coordinate and momentum pair lies within the phase |
212 | | - space region limited by the action value limit_n_rms * epsn_rms. |
213 | | -
|
214 | | - Coordinate u and momentum up are assumed to be connected to the |
215 | | - amplitude J via the twiss_beta value, |
216 | | - J = sqrt(u^2 + twiss_beta^2 up^2) . |
| 266 | + space region limited by the action value |
| 267 | + limit_n_rms * rms_amplitude. |
| 268 | + The closure acts on normalised Floquet space, i.e. do apply this |
| 269 | + function to the particles before matching to the optics values. |
| 270 | +
|
| 271 | + Coordinate u and momentum up are squared to give the action |
| 272 | + amplitude |
| 273 | + J = u^2 + up^2 . |
217 | 274 | The amplitude is required to be below the limit to be accepted, |
218 | | - J < limit_n_rms * epsn_rms. |
| 275 | + J < limit_n_rms * rms_amplitude. |
219 | 276 | The usual use case will be generating u and up in normalised Floquet |
220 | 277 | space (i.e. before the normalised phase space coordinates |
221 | 278 | get matched to the optics or longitudinal eta and Qs). |
222 | | - In this case, twiss_beta takes the default value 1 in normalised |
223 | | - Floquet space. Consequently, the 1 sigma RMS reference value |
| 279 | + Consequently, the 1 sigma RMS reference value |
224 | 280 | epsn_rms corresponds to the normalised 1 sigma RMS emittance |
225 | 281 | (i.e. amounting to beam.epsn_x() and beam.epsn_y() in the transverse |
226 | 282 | plane, and beam.epsn_z()/4 in the longitudinal plane). |
227 | 283 | ''' |
228 | | - threshold_amplitude_squared = (limit_n_rms * epsn_rms)**2 |
| 284 | + if epsn_rms: |
| 285 | + # backwards compatibility (it was bad naming): |
| 286 | + limit_n_rms *= limit_n_rms |
| 287 | + assert rms_amplitude == None, \ |
| 288 | + ("epsn_rms is for backwards compatibility, it has been " |
| 289 | + "replaced by its sqrt-value rms_amplitude. Please do not " |
| 290 | + "use both at the same time!") |
| 291 | + rms_amplitude = epsn_rms**2 |
| 292 | + threshold_amplitude = limit_n_rms * rms_amplitude |
229 | 293 | def is_accepted(u, up): |
230 | | - Jsq = u**2 + (twiss_beta * up)**2 |
231 | | - return Jsq < threshold_amplitude_squared |
| 294 | + Jsq = u**2 + up**2 |
| 295 | + return Jsq < threshold_amplitude |
232 | 296 | return is_accepted |
233 | 297 |
|
234 | 298 |
|
|
0 commit comments