# -*- coding: utf-8 -*-
"""
This module defines the impedances and wake functions from the resonator model
based on the WakeField class.
"""
import numpy as np
from numpy.typing import NDArray
from scipy.constants import c, pi
from mbtrack2.impedance.wakefield import Impedance, WakeField, WakeFunction
[docs]
class Resonator(WakeField):
"""
Resonator model WakeField element which computes the impedance and the
wake function in both longitudinal and transverse case.
! For transverse case, if Q < 2, there is a kick factor mismatch
between time and frequency domain.
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
Shunt impedance in [ohm] in longitudinal or [ohm/m] in transverse.
fr : float
Resonance frequency in [Hz].
Q : float
Quality factor.
plane : str or list
Plane on which the resonator is used: "long", "x" or "y".
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.
Properties
----------
bandwidth : float
Resonator bandwitdh in [Hz].
ring_down_length : float
Resonator ring down length in [m].
References
----------
[1] B. W. Zotter and S. A. Kheifets, "Impedances and Wakes in High-Energy
Particle Ac-celerators", Eq. (3.10) and (3.15), pp.51-53.
"""
[docs]
def __init__(self,
time: NDArray,
frequency: NDArray,
Rs: float,
fr: float,
Q: float,
plane: str | list[str],
atol: float = 1e-20):
super().__init__()
self.Rs = Rs
self.fr = fr
self.wr = 2 * np.pi * self.fr
self.Q = Q
if isinstance(plane, str):
self.plane = [plane]
elif isinstance(plane, list):
self.plane = plane
if self.Q >= 0.5:
self.Q_p = np.sqrt(self.Q**2 - 0.25)
else:
self.Q_p = np.sqrt(0.25 - self.Q**2)
self.wr_p = (self.wr * self.Q_p) / self.Q
for dim in self.plane:
if dim == "long":
Zlong = Impedance(variable=frequency,
function=self.long_impedance(frequency),
component_type="long")
super().append_to_model(Zlong)
Wlong = WakeFunction(variable=time,
function=self.long_wake_function(
time, atol),
component_type="long")
super().append_to_model(Wlong)
elif dim in ["x", "y"]:
Zdip = Impedance(variable=frequency,
function=self.transverse_impedance(frequency),
component_type=dim + "dip")
super().append_to_model(Zdip)
Wdip = WakeFunction(
variable=time,
function=self.transverse_wake_function(time),
component_type=dim + "dip")
super().append_to_model(Wdip)
else:
raise ValueError("Plane must be: long, x or y")
[docs]
def long_wake_function(self, t: NDArray, atol: float) -> NDArray:
if self.Q >= 0.5:
wl = ((self.wr * self.Rs / self.Q) * np.exp(-1 * self.wr * t /
(2 * self.Q)) *
(np.cos(self.wr_p * t) - np.sin(self.wr_p * t) /
(2 * self.Q_p)))
else:
wl = ((self.wr * self.Rs / self.Q) * np.exp(-1 * self.wr * t /
(2 * self.Q)) *
(np.cosh(self.wr_p * t) - 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
if np.any(t < -atol):
wl[t < -atol] = 0
return wl
[docs]
def long_impedance(self, f: NDArray) -> NDArray:
return self.Rs / (1 + 1j * self.Q * (f / self.fr - self.fr / f))
[docs]
def transverse_impedance(self, f: NDArray) -> NDArray:
return self.Rs * self.fr / f / (1 + 1j * self.Q *
(f / self.fr - self.fr / f))
[docs]
def transverse_wake_function(self, t: NDArray) -> NDArray:
if self.Q >= 0.5:
wt = (self.wr * self.Rs / self.Q_p *
np.exp(-1 * t * self.wr / 2 / self.Q_p) *
np.sin(self.wr_p * t))
else:
wt = (self.wr * self.Rs / self.Q_p *
np.exp(-1 * t * self.wr / 2 / self.Q_p) *
np.sinh(self.wr_p * t))
if np.any(t < 0):
wt[t < 0] = 0
return wt
@property
def bandwidth(self) -> float:
"""Return resonator bandwitdh in [Hz]."""
return self.fr / self.Q
@property
def ring_down_length(self) -> float:
"""Return resonator ring down length in [m]."""
return c / (pi * self.bandwidth)
[docs]
class PureInductive(WakeField):
"""
Pure inductive Wakefield element which computes associated longitudinal
impedance and wake function.
Parameters
----------
L : float
Inductance value in [Ohm/Hz].
n_wake : int or float, optional
Number of points used in the wake function.
n_imp : int or float, optional
Number of points used in the impedance.
imp_freq_lim : float, optional
Maximum frequency used in the impedance.
nout, trim : see Impedance.to_wakefunction
"""
[docs]
def __init__(self,
L: float,
n_wake: int | float = 1e6,
n_imp: int | float = 1e6,
imp_freq_lim: float = 1e11,
nout: int | float | None = None,
trim: bool = False):
super().__init__()
self.L = L
self.n_wake = int(n_wake)
self.n_imp = int(n_imp)
self.imp_freq_lim = imp_freq_lim
freq = np.linspace(start=1, stop=self.imp_freq_lim, num=self.n_imp)
imp = Impedance(variable=freq,
function=self.long_impedance(freq),
component_type="long")
super().append_to_model(imp)
wf = imp.to_wakefunction(nout=nout, trim=trim)
super().append_to_model(wf)
[docs]
def long_impedance(self, f: NDArray) -> NDArray:
return 1j * self.L * f
[docs]
class PureResistive(WakeField):
"""
Pure resistive Wakefield element which computes associated longitudinal
impedance and wake function.
Parameters
----------
R : float
Resistance value in [Ohm].
n_wake : int or float, optional
Number of points used in the wake function.
n_imp : int or float, optional
Number of points used in the impedance.
imp_freq_lim : float, optional
Maximum frequency used in the impedance.
nout, trim : see Impedance.to_wakefunction
"""
[docs]
def __init__(self,
R: float,
n_wake: int | float = 1e6,
n_imp: int | float = 1e6,
imp_freq_lim: float = 1e11,
nout: int | float | None = None,
trim: bool = False):
super().__init__()
self.R = R
self.n_wake = int(n_wake)
self.n_imp = int(n_imp)
self.imp_freq_lim = imp_freq_lim
freq = np.linspace(start=1, stop=self.imp_freq_lim, num=self.n_imp)
imp = Impedance(variable=freq,
function=self.long_impedance(freq),
component_type="long")
super().append_to_model(imp)
wf = imp.to_wakefunction(nout=nout, trim=trim)
super().append_to_model(wf)
[docs]
def long_impedance(self, f: NDArray) -> NDArray:
return np.full_like(f, self.R, dtype=float)