Skip to content

Commit 9ceab6b

Browse files
committed
Merge branch 'feature/synchrotron' into develop
2 parents 1e663e9 + 4752657 commit 9ceab6b

File tree

6 files changed

+4293
-0
lines changed

6 files changed

+4293
-0
lines changed

machines/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .. import Element, Printing, clean_slices, utils
2+
from .. import __version__

machines/synchrotron.py

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
from __future__ import division
2+
3+
import numpy as np
4+
from scipy.constants import c, e
5+
6+
from PyHEADTAIL.general.element import Element
7+
import PyHEADTAIL.particles.generators as gen
8+
9+
try:
10+
from PyHEADTAIL.trackers.transverse_tracking_cython import TransverseMap
11+
from PyHEADTAIL.trackers.detuners_cython import (Chromaticity,
12+
AmplitudeDetuning)
13+
except ImportError as e:
14+
print ("*** Warning: could not import cython variants of trackers, "
15+
"did you cythonize (use the following command)?\n"
16+
"$ ./install \n"
17+
"Falling back to (slower) python version.")
18+
from PyHEADTAIL.trackers.transverse_tracking import TransverseMap
19+
from PyHEADTAIL.trackers.detuners import Chromaticity, AmplitudeDetuning
20+
21+
from PyHEADTAIL.trackers.simple_long_tracking import LinearMap, RFSystems
22+
23+
class BasicSynchrotron(Element):
24+
def __init__(self, optics_mode, circumference=None, n_segments=None, s=None, name=None,
25+
alpha_x=None, beta_x=None, D_x=None, alpha_y=None, beta_y=None, D_y=None,
26+
accQ_x=None, accQ_y=None, Qp_x=0, Qp_y=0, app_x=0, app_y=0, app_xy=0,
27+
alpha_mom_compaction=None, longitudinal_mode=None, Q_s=None,
28+
h_RF=None, V_RF=None, dphi_RF=None, p0=None, p_increment=None,
29+
charge=None, mass=None, **kwargs):
30+
31+
32+
self.optics_mode = optics_mode
33+
self.longitudinal_mode = longitudinal_mode
34+
self.charge = charge
35+
self.mass = mass
36+
self.p0 = p0
37+
38+
self.one_turn_map = []
39+
40+
detuners = []
41+
if Qp_x != 0 or Qp_y != 0:
42+
detuners.append(Chromaticity(Qp_x, Qp_y))
43+
if app_x != 0 or app_y != 0 or app_xy != 0:
44+
detuners.append(AmplitudeDetuning(app_x, app_y, app_xy))
45+
46+
# construct transverse map
47+
self._contruct_transverse_map(optics_mode=optics_mode, circumference=circumference, n_segments=n_segments, s=s, name=name,
48+
alpha_x=alpha_x, beta_x=beta_x, D_x=D_x, alpha_y=alpha_y, beta_y=beta_y, D_y=D_y,
49+
accQ_x=accQ_x, accQ_y=accQ_y, detuners=detuners)
50+
51+
# construct longitudinal map
52+
self._contruct_longitudinal_map(alpha_mom_compaction=alpha_mom_compaction, longitudinal_mode=longitudinal_mode, Q_s=Q_s,
53+
h_RF=h_RF, V_RF=V_RF, dphi_RF=dphi_RF, p_increment=p_increment)
54+
55+
56+
57+
@property
58+
def gamma(self):
59+
return self._gamma
60+
@gamma.setter
61+
def gamma(self, value):
62+
self._gamma = value
63+
self._beta = np.sqrt(1 - self.gamma**-2)
64+
self._betagamma = np.sqrt(self.gamma**2 - 1)
65+
self._p0 = self.betagamma * self.mass * c
66+
67+
@property
68+
def beta(self):
69+
return self._beta
70+
@beta.setter
71+
def beta(self, value):
72+
self.gamma = 1. / np.sqrt(1-value**2)
73+
74+
@property
75+
def betagamma(self):
76+
return self._betagamma
77+
@betagamma.setter
78+
def betagamma(self, value):
79+
self.gamma = np.sqrt(value**2 + 1)
80+
81+
@property
82+
def p0(self):
83+
return self._p0
84+
@p0.setter
85+
def p0(self, value):
86+
self.gamma = 1 / (c * self.mass) * np.sqrt(value**2+self.mass**2*c**2)
87+
88+
@property
89+
def Q_x(self):
90+
return np.atleast_1d(self.transverse_map.accQ_x)[-1]
91+
92+
@property
93+
def Q_y(self):
94+
return np.atleast_1d(self.transverse_map.accQ_y)[-1]
95+
96+
def track(self, bunch, verbose=False):
97+
for m in self.one_turn_map:
98+
if verbose:
99+
self.prints('Tracking through:\n' + str(m))
100+
m.track(bunch)
101+
102+
def install_after_each_transverse_segment(self, element_to_add):
103+
'''Attention: Do not add any elements which update the dispersion!'''
104+
one_turn_map_new = []
105+
for element in self.one_turn_map:
106+
one_turn_map_new.append(element)
107+
if element in self.transverse_map:
108+
one_turn_map_new.append(element_to_add)
109+
self.one_turn_map = one_turn_map_new
110+
111+
def generate_6D_Gaussian_bunch(self, n_macroparticles, intensity,
112+
epsn_x, epsn_y, sigma_z):
113+
'''Generate a 6D Gaussian distribution of particles which is
114+
transversely matched to the Synchrotron. Longitudinally, the
115+
distribution is matched only in terms of linear focusing.
116+
For a non-linear bucket, the Gaussian distribution is cut along
117+
the separatrix (with some margin). It will gradually filament
118+
into the bucket. This will change the specified bunch length.
119+
'''
120+
if self.longitudinal_mode == 'linear':
121+
check_inside_bucket = lambda z,dp : np.array(len(z)*[True])
122+
elif self.longitudinal_mode == 'non-linear':
123+
check_inside_bucket = self.longitudinal_map.get_bucket(
124+
gamma=self.gamma).make_is_accepted(margin=0.05)
125+
else:
126+
raise NotImplementedError(
127+
'Something wrong with self.longitudinal_mode')
128+
129+
eta = self.longitudinal_map.alpha_array[0] - self.gamma**-2
130+
beta_z = np.abs(eta)*self.circumference/2./np.pi/self.longitudinal_map.Qs
131+
sigma_dp = sigma_z/beta_z
132+
epsx_geo = epsn_x/self.betagamma
133+
epsy_geo = epsn_y/self.betagamma
134+
135+
injection_optics = self.transverse_map.get_injection_optics()
136+
137+
bunch = gen.ParticleGenerator(macroparticlenumber=n_macroparticles,
138+
intensity=intensity, charge=self.charge, mass=self.mass,
139+
circumference=self.circumference, gamma=self.gamma,
140+
distribution_x = gen.gaussian2D(epsx_geo), alpha_x=injection_optics['alpha_x'], beta_x=injection_optics['beta_x'], D_x=injection_optics['D_x'],
141+
distribution_y = gen.gaussian2D(epsy_geo), alpha_y=injection_optics['alpha_y'], beta_y=injection_optics['beta_y'], D_y=injection_optics['D_y'],
142+
distribution_z = gen.cut_distribution(gen.gaussian2D_asymmetrical(sigma_u=sigma_z, sigma_up=sigma_dp),is_accepted=check_inside_bucket),
143+
).generate()
144+
145+
return bunch
146+
147+
def generate_6D_Gaussian_bunch_matched(
148+
self, n_macroparticles, intensity, epsn_x, epsn_y,
149+
sigma_z=None, epsn_z=None):
150+
'''Generate a 6D Gaussian distribution of particles which is
151+
transversely as well as longitudinally matched.
152+
The distribution is found iteratively to exactly yield the
153+
given bunch length while at the same time being stationary in
154+
the non-linear bucket. Thus, the bunch length should amount
155+
to the one specificed and should not change significantly
156+
during the synchrotron motion.
157+
158+
Requires self.longitudinal_mode == 'non-linear'
159+
for the bucket.
160+
'''
161+
assert self.longitudinal_mode == 'non-linear'
162+
epsx_geo = epsn_x/self.betagamma
163+
epsy_geo = epsn_y/self.betagamma
164+
165+
injection_optics = self.transverse_map.get_injection_optics()
166+
167+
bunch = gen.ParticleGenerator(macroparticlenumber=n_macroparticles,
168+
intensity=intensity, charge=self.charge, mass=self.mass,
169+
circumference=self.circumference, gamma=self.gamma,
170+
distribution_x = gen.gaussian2D(epsx_geo), alpha_x=injection_optics['alpha_x'], beta_x=injection_optics['beta_x'], D_x=injection_optics['D_x'],
171+
distribution_y = gen.gaussian2D(epsy_geo), alpha_y=injection_optics['alpha_y'], beta_y=injection_optics['beta_y'], D_y=injection_optics['D_y'],
172+
distribution_z = gen.RF_bucket_distribution(self.longitudinal_map.get_bucket(gamma=self.gamma), sigma_z=sigma_z, epsn_z=epsn_z),
173+
).generate()
174+
175+
return bunch
176+
177+
def _contruct_transverse_map(self, optics_mode=None, circumference=None, n_segments=None, s=None, name=None,
178+
alpha_x=None, beta_x=None, D_x=None, alpha_y=None, beta_y=None, D_y=None,
179+
accQ_x=None, accQ_y=None, detuners=[]):
180+
181+
if optics_mode == 'smooth':
182+
if circumference is None:
183+
raise ValueError('circumference has to be specified if optics_mode = "smooth"')
184+
185+
if n_segments is None:
186+
raise ValueError('n_segments has to be specified if optics_mode = "smooth"')
187+
188+
if s is not None:
189+
raise ValueError('s vector cannot be provided if optics_mode = "smooth"')
190+
191+
192+
s = (np.arange(0, n_segments + 1)
193+
* circumference / n_segments)
194+
195+
alpha_x=0.*s
196+
beta_x=0.*s+beta_x
197+
D_x=0.*s+D_x
198+
alpha_y=0.*s
199+
beta_y=0.*s+beta_y
200+
D_y=0.*s+D_y
201+
202+
elif optics_mode == 'non-smooth':
203+
if circumference is not None:
204+
raise ValueError('circumference cannot be provided if optics_mode = "non-smooth"')
205+
206+
if n_segments is not None:
207+
raise ValueError('n_segments cannot be provided if optics_mode = "non-smooth"')
208+
209+
if s is None:
210+
raise ValueError('s has to be specified if optics_mode = "smooth"')
211+
212+
else:
213+
raise ValueError('optics_mode not recognized')
214+
215+
self.transverse_map = TransverseMap(s=s,
216+
alpha_x=alpha_x,
217+
beta_x=beta_x,
218+
D_x=D_x,
219+
alpha_y=alpha_y,
220+
beta_y=beta_y,
221+
D_y=D_y,
222+
accQ_x=accQ_x, accQ_y=accQ_y, detuners=detuners)
223+
224+
self.circumference = s[-1]
225+
self.transverse_map.n_segments = len(s)-1
226+
227+
if name is None:
228+
self.transverse_map.name = ['P_%d'%ip for ip in xrange(len(s)-1)]
229+
self.transverse_map.name.append('end_ring')
230+
else:
231+
self.transverse_map.name = name
232+
233+
for i_seg, m in enumerate(self.transverse_map):
234+
m.i0 = i_seg
235+
m.i1 = i_seg+1
236+
m.s0 = self.transverse_map.s[i_seg]
237+
m.s1 = self.transverse_map.s[i_seg+1]
238+
m.name0 = self.transverse_map.name[i_seg]
239+
m.name1 = self.transverse_map.name[i_seg+1]
240+
m.beta_x0 = self.transverse_map.beta_x[i_seg]
241+
m.beta_x1 = self.transverse_map.beta_x[i_seg+1]
242+
m.beta_y0 = self.transverse_map.beta_y[i_seg]
243+
m.beta_y1 = self.transverse_map.beta_y[i_seg+1]
244+
245+
# insert transverse map in the ring
246+
for m in self.transverse_map:
247+
self.one_turn_map.append(m)
248+
249+
def _contruct_longitudinal_map(self, alpha_mom_compaction=None, longitudinal_mode=None, Q_s=None,
250+
h_RF=None, V_RF=None, dphi_RF=None, p_increment=None):
251+
252+
# compute the index of the element before which to insert
253+
# the longitudinal map
254+
if longitudinal_mode is not None:
255+
for insert_before, si in enumerate(self.transverse_map.s):
256+
if si > 0.5 * self.circumference:
257+
break
258+
259+
if longitudinal_mode == 'linear':
260+
261+
eta = alpha_mom_compaction - self.gamma**-2
262+
263+
if Q_s == None:
264+
if p_increment!=0 or dphi_RF!=0:
265+
raise ValueError('Formula not valid in this case!!!!')
266+
else:
267+
Q_s = np.sqrt( e*np.abs(eta)*(h_RF*V_RF)
268+
/ (2*np.pi*self.p0*self.beta*c) )
269+
270+
self.longitudinal_map = LinearMap(
271+
np.atleast_1d(alpha_mom_compaction),
272+
self.circumference, Q_s,
273+
D_x=self.transverse_map.D_x[insert_before],
274+
D_y=self.transverse_map.D_y[insert_before])
275+
276+
277+
elif longitudinal_mode == 'non-linear':
278+
self.longitudinal_map = RFSystems(
279+
self.circumference, np.atleast_1d(h_RF),
280+
np.atleast_1d(V_RF), np.atleast_1d(dphi_RF),
281+
np.atleast_1d(alpha_mom_compaction), self.gamma, p_increment,
282+
D_x=self.transverse_map.D_x[insert_before],
283+
D_y=self.transverse_map.D_y[insert_before],
284+
mass=self.mass, charge=self.charge
285+
)
286+
else:
287+
raise NotImplementedError(
288+
'Something wrong with longitudinal_mode')
289+
290+
self.one_turn_map.insert(insert_before, self.longitudinal_map)

0 commit comments

Comments
 (0)