Skip to content
Snippets Groups Projects
Commit f0f97c3a authored by Alexis GAMELIN's avatar Alexis GAMELIN
Browse files

Merge branch 'RW_LR' into Prepare_version

parents c5a79d24 f3296289
No related branches found
No related tags found
No related merge requests found
...@@ -62,6 +62,10 @@ class CircularResistiveWall(WakeField): ...@@ -62,6 +62,10 @@ class CircularResistiveWall(WakeField):
exact : bool, optional exact : bool, optional
If False, approxmiated formulas are used for the wake function If False, approxmiated formulas are used for the wake function
computations. computations.
atol : float, optional
Absolute tolerance used to enforce fundamental theorem of beam loading
for the exact expression of the longitudinal wake function.
Default is 1e-20.
References References
---------- ----------
...@@ -74,12 +78,15 @@ class CircularResistiveWall(WakeField): ...@@ -74,12 +78,15 @@ class CircularResistiveWall(WakeField):
""" """
def __init__(self, time, frequency, length, rho, radius, exact=False): def __init__(self, time, frequency, length, rho, radius, exact=False,
atol=1e-20):
super().__init__() super().__init__()
self.length = length self.length = length
self.rho = rho self.rho = rho
self.radius = radius self.radius = radius
self.Z0 = mu_0*c
self.t0 = (2*self.rho*self.radius**2 / self.Z0)**(1/3) / c
omega = 2*np.pi*frequency omega = 2*np.pi*frequency
Z1 = length*(1 + np.sign(frequency)*1j)*rho/( Z1 = length*(1 + np.sign(frequency)*1j)*rho/(
...@@ -87,7 +94,7 @@ class CircularResistiveWall(WakeField): ...@@ -87,7 +94,7 @@ class CircularResistiveWall(WakeField):
Z2 = c/omega*length*(1 + np.sign(frequency)*1j)*rho/( Z2 = c/omega*length*(1 + np.sign(frequency)*1j)*rho/(
np.pi*radius**3*skin_depth(frequency,rho)) np.pi*radius**3*skin_depth(frequency,rho))
Wl = self.LongitudinalWakeFunction(time, exact) Wl = self.LongitudinalWakeFunction(time, exact, atol)
Wt = self.TransverseWakeFunction(time, exact) Wt = self.TransverseWakeFunction(time, exact)
Zlong = Impedance(variable = frequency, function = Z1, impedance_type='long') Zlong = Impedance(variable = frequency, function = Z1, impedance_type='long')
...@@ -104,12 +111,15 @@ class CircularResistiveWall(WakeField): ...@@ -104,12 +111,15 @@ class CircularResistiveWall(WakeField):
super().append_to_model(Wxdip) super().append_to_model(Wxdip)
super().append_to_model(Wydip) super().append_to_model(Wydip)
def LongitudinalWakeFunction(self, time, exact=False): def LongitudinalWakeFunction(self, time, exact=False, atol=1e-20):
""" """
Compute the longitudinal wake function of a circular resistive wall Compute the longitudinal wake function of a circular resistive wall
using Eq. (22), or approxmiated expression Eq. (24), of [1]. The using Eq. (22), or approxmiated expression Eq. (24), of [1]. The
approxmiated expression is valid if the time is large compared to the approxmiated expression is valid if the time is large compared to the
characteristic time t0. characteristic time t0.
If some time value is smaller than atol, then the fundamental theorem
of beam loading is applied: Wl(0) = Wl(0+)/2.
Parameters Parameters
---------- ----------
...@@ -117,6 +127,10 @@ class CircularResistiveWall(WakeField): ...@@ -117,6 +127,10 @@ class CircularResistiveWall(WakeField):
Time points where the wake function is evaluated in [s]. Time points where the wake function is evaluated in [s].
exact : bool, optional exact : bool, optional
If True, the exact expression is used. The default is False. If True, the exact expression is used. The default is False.
atol : float, optional
Absolute tolerance used to enforce fundamental theorem of beam loading
for the exact expression of the longitudinal wake function.
Default is 1e-20.
Returns Returns
------- -------
...@@ -130,30 +144,17 @@ class CircularResistiveWall(WakeField): ...@@ -130,30 +144,17 @@ class CircularResistiveWall(WakeField):
and Methods in Physics Research Section A: Accelerators, Spectrometers, and Methods in Physics Research Section A: Accelerators, Spectrometers,
Detectors and Associated Equipment 806 (2016): 221-230. Detectors and Associated Equipment 806 (2016): 221-230.
""" """
wl = np.zeros_like(time)
Z0 = mu_0*c idx1 = time < 0
wl[idx1] = 0
if exact==True: if exact==True:
self.t0 = (2*self.rho*self.radius**2 / Z0)**(1/3) / c idx2 = time > 20 * self.t0
factor = 4*Z0*c/(np.pi * self.radius**2) * self.length * -1 idx3 = np.logical_not(np.logical_or(idx1,idx2))
wl = np.zeros_like(time) wl[idx3] = self.__LongWakeExact(time[idx3], atol)
for i, t in enumerate(time):
val, err = quad(lambda z:self.function(t, z), 0, np.inf)
if t < 0:
wl[i] = 0
else:
wl[i] = factor * ( np.exp(-t/self.t0) / 3 *
np.cos( np.sqrt(3) * t / self.t0 )
- np.sqrt(2) / np.pi * val )
else: else:
wl = (1/(4*np.pi * self.radius) idx2 = np.logical_not(idx1)
* np.sqrt(Z0 * self.rho / (c * np.pi) ) wl[idx2] = self.__LongWakeApprox(time[idx2])
/ time**(3/2) ) * self.length return wl
ind = np.isnan(wl)
wl[ind] = 0
return -1*wl
def TransverseWakeFunction(self, time, exact=False): def TransverseWakeFunction(self, time, exact=False):
""" """
...@@ -183,35 +184,59 @@ class CircularResistiveWall(WakeField): ...@@ -183,35 +184,59 @@ class CircularResistiveWall(WakeField):
and Methods in Physics Research Section A: Accelerators, Spectrometers, and Methods in Physics Research Section A: Accelerators, Spectrometers,
Detectors and Associated Equipment 806 (2016): 221-230. Detectors and Associated Equipment 806 (2016): 221-230.
""" """
wt = np.zeros_like(time)
Z0 = mu_0*c idx1 = time < 0
wt[idx1] = 0
if exact==True: if exact==True:
self.t0 = (2*self.rho*self.radius**2 / Z0)**(1/3) / c idx2 = time > 20 * self.t0
factor = -1 * (8 * Z0 * c**2 * self.t0) / (np.pi * self.radius**4) * self.length idx3 = np.logical_not(np.logical_or(idx1,idx2))
wt = np.zeros_like(time) wt[idx3] = self.__TransWakeExact(time[idx3])
else:
for i, t in enumerate(time): idx2 = np.logical_not(idx1)
val, err = quad(lambda z:self.function2(t, z), 0, np.inf) wt[idx2] = self.__TransWakeApprox(time[idx2])
if t < 0: return wt
wt[i] = 0
else: def __LongWakeExact(self, time, atol):
wt[i] = factor * ( 1 / 12 * (-1 * np.exp(-t/self.t0) * wl = np.zeros_like(time)
factor = 4*self.Z0*c/(np.pi * self.radius**2) * self.length
for i, t in enumerate(time):
val, err = quad(lambda z:self.__function(t, z), 0, np.inf)
wl[i] = factor * ( np.exp(-t/self.t0) / 3 *
np.cos( np.sqrt(3) * t / self.t0 )
- np.sqrt(2) / np.pi * val )
if np.isclose(0, t, atol=atol):
wl[i] = wl[i]/2
return wl
def __TransWakeExact(self, time):
wt = np.zeros_like(time)
factor = ((8 * self.Z0 * c**2 * self.t0) / (np.pi * self.radius**4) *
self.length)
for i, t in enumerate(time):
val, err = quad(lambda z:self.__function2(t, z), 0, np.inf)
wt[i] = factor * ( 1 / 12 * (-1 * np.exp(-t/self.t0) *
np.cos( np.sqrt(3) * t / self.t0 ) + np.cos( np.sqrt(3) * t / self.t0 ) +
np.sqrt(3) * np.exp(-t/self.t0) * np.sqrt(3) * np.exp(-t/self.t0) *
np.sin( np.sqrt(3) * t / self.t0 ) ) - np.sin( np.sqrt(3) * t / self.t0 ) ) -
np.sqrt(2) / np.pi * val ) np.sqrt(2) / np.pi * val )
else: return wt
wt = (1 / (np.pi * self.radius**3) * np.sqrt(Z0 * c * self.rho / np.pi)
/ time**(1/2) * self.length * -1) def __LongWakeApprox(self, t):
ind = np.isnan(wt) wl = - 1 * ( 1 / (4*np.pi * self.radius) *
wt[ind] = 0 np.sqrt(self.Z0 * self.rho / (c * np.pi) ) /
return -1*wt t ** (3/2) ) * self.length
return wl
def function(self, t, x):
def __TransWakeApprox(self, t):
wt = (1 / (np.pi * self.radius**3) *
np.sqrt(self.Z0 * c * self.rho / np.pi)
/ t ** (1/2) * self.length)
return wt
def __function(self, t, x):
return ( (x**2 * np.exp(-1* (x**2) * t / self.t0) ) / (x**6 + 8) ) return ( (x**2 * np.exp(-1* (x**2) * t / self.t0) ) / (x**6 + 8) )
def function2(self, t, x): def __function2(self, t, x):
return ( (-1 * np.exp(-1* (x**2) * t / self.t0) ) / (x**6 + 8) ) return ( (-1 * np.exp(-1* (x**2) * t / self.t0) ) / (x**6 + 8) )
class Coating(WakeField): class Coating(WakeField):
......
...@@ -12,27 +12,29 @@ from mbtrack2.collective_effects.wakefield import (WakeField, Impedance, ...@@ -12,27 +12,29 @@ from mbtrack2.collective_effects.wakefield import (WakeField, Impedance,
WakeFunction) WakeFunction)
class Resonator(WakeField): class Resonator(WakeField):
def __init__(self, Rs, fr, Q, plane, n_wake=1e6, n_imp=1e6, imp_freq_lim=100e9): def __init__(self, time, frequency, Rs, fr, Q, plane, atol=1e-20):
""" """
Resonator model WakeField element which computes the impedance and the Resonator model WakeField element which computes the impedance and the
wake function in both longitudinal and transverse case. wake function in both longitudinal and transverse case.
Parameters Parameters
---------- ----------
time : array of float
Time points where the wake function will be evaluated in [s].
frequency : array of float
Frequency points where the impedance will be evaluated in [Hz].
Rs : float Rs : float
Shunt impedance in [ohm]. Shunt impedance in [ohm].
fr : float fr : float
Resonance frequency in [Hz]. Resonance frequency in [Hz].
Q : float Q : float
Quality factor. Quality factor.
plane : str plane : str or list
Plane on which the resonator is used: "long", "x" or "y". Plane on which the resonator is used: "long", "x" or "y".
n_wake : int or float, optional atol : float, optional
Number of points used in the wake function. Absolute tolerance used to enforce fundamental theorem of beam
n_imp : int or float, optional loading for the exact expression of the longitudinal wake function.
Number of points used in the impedance. Default is 1e-20.
imp_freq_lim : float, optional
Maximum frequency used in the impedance.
References References
---------- ----------
...@@ -45,62 +47,54 @@ class Resonator(WakeField): ...@@ -45,62 +47,54 @@ class Resonator(WakeField):
self.fr = fr self.fr = fr
self.wr = 2 * np.pi * self.fr self.wr = 2 * np.pi * self.fr
self.Q = Q self.Q = Q
self.n_wake = int(n_wake) if isinstance(plane, str):
self.n_imp = int(n_imp) self.plane = [plane]
self.imp_freq_lim = imp_freq_lim elif isinstance(plane, list):
self.plane = plane self.plane = plane
self.timestop = round(np.log(1000)/self.wr*2*self.Q, 12)
if self.Q >= 0.5: if self.Q >= 0.5:
self.Q_p = np.sqrt(self.Q**2 - 0.25) self.Q_p = np.sqrt(self.Q**2 - 0.25)
else: else:
self.Q_p = np.sqrt(0.25 - self.Q**2) self.Q_p = np.sqrt(0.25 - self.Q**2)
self.wr_p = (self.wr*self.Q_p)/self.Q self.wr_p = (self.wr*self.Q_p)/self.Q
if self.plane == "long": for dim in self.plane:
if dim == "long":
freq = np.linspace(start=1, stop=self.imp_freq_lim, num=self.n_imp) Zlong = Impedance(variable=frequency,
imp = Impedance(variable=freq, function=self.long_impedance(frequency),
function=self.long_impedance(freq), impedance_type="long")
impedance_type="long") super().append_to_model(Zlong)
super().append_to_model(imp) Wlong = WakeFunction(variable=time,
function=self.long_wake_function(time, atol),
time = np.linspace(start=0, stop=self.timestop, num=self.n_wake) wake_type="long")
wake = WakeFunction(variable=time, super().append_to_model(Wlong)
function=self.long_wake_function(time),
wake_type="long") elif dim == "x" or dim == "y":
super().append_to_model(wake) Zdip = Impedance(variable=frequency,
function=self.transverse_impedance(frequency),
elif self.plane == "x" or self.plane == "y" : impedance_type=dim + "dip")
super().append_to_model(Zdip)
freq = np.linspace(start=1, stop=self.imp_freq_lim, num=self.n_imp) Wdip = WakeFunction(variable=time,
imp = Impedance(variable=freq, function=self.transverse_wake_function(time),
function=self.transverse_impedance(freq), wake_type=dim + "dip")
impedance_type=self.plane + "dip") super().append_to_model(Wdip)
super().append_to_model(imp) else:
raise ValueError("Plane must be: long, x or y")
time = np.linspace(start=0, stop=self.timestop, num=self.n_wake)
wake = WakeFunction(variable=time,
function=self.transverse_wake_function(time),
wake_type=self.plane + "dip")
super().append_to_model(wake)
else:
raise ValueError("Plane must be: long, x or y")
def long_wake_function(self, t): def long_wake_function(self, t, atol):
if self.Q >= 0.5: if self.Q >= 0.5:
return ( (self.wr * self.Rs / self.Q) * wl = ( (self.wr * self.Rs / self.Q) *
np.exp(-1* self.wr * t / (2 * self.Q) ) * np.exp(-1* self.wr * t / (2 * self.Q) ) *
(np.cos(self.wr_p * t) - (np.cos(self.wr_p * t) -
np.sin(self.wr_p * t) / (2 * self.Q_p) ) ) np.sin(self.wr_p * t) / (2 * self.Q_p) ) )
elif self.Q < 0.5: elif self.Q < 0.5:
return ( (self.wr * self.Rs / self.Q) * wl = ( (self.wr * self.Rs / self.Q) *
np.exp(-1* self.wr * t / (2 * self.Q) ) * np.exp(-1* self.wr * t / (2 * self.Q) ) *
(np.cosh(self.wr_p * t) - (np.cosh(self.wr_p * t) -
np.sinh(self.wr_p * t) / (2 * self.Q_p) ) ) np.sinh(self.wr_p * t) / (2 * self.Q_p) ) )
if np.any(np.abs(t) < atol):
wl[np.abs(t) < atol] = wl[np.abs(t) < atol]/2
return wl
def long_impedance(self, f): def long_impedance(self, f):
return self.Rs / (1 + 1j * self.Q * (f/self.fr - self.fr/f)) return self.Rs / (1 + 1j * self.Q * (f/self.fr - self.fr/f))
......
...@@ -690,14 +690,16 @@ class WakeField: ...@@ -690,14 +690,16 @@ class WakeField:
""" """
Return an array of the impedance component names for the element. Return an array of the impedance component names for the element.
""" """
return np.array([comp for comp in dir(self) if re.match(r'[Z]', comp)]) valid = ["Zlong", "Zxdip", "Zydip", "Zxquad", "Zyquad"]
return np.array([comp for comp in dir(self) if comp in valid])
@property @property
def wake_components(self): def wake_components(self):
""" """
Return an array of the wake function component names for the element. Return an array of the wake function component names for the element.
""" """
return np.array([comp for comp in dir(self) if re.match(r'[W]', comp)]) valid = ["Wlong", "Wxdip", "Wydip", "Wxquad", "Wyquad"]
return np.array([comp for comp in dir(self) if comp in valid])
@staticmethod @staticmethod
def add_wakefields(wake1, beta1, wake2, beta2): def add_wakefields(wake1, beta1, wake2, beta2):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment