Source code for at.collective.wake_object

"""
Wake object creation
"""
import numpy
import warnings
from enum import Enum
from scipy.interpolate import interp1d
from ..lattice import AtWarning, AtError
from .wake_functions import long_resonator_wf, transverse_resonator_wf
from .wake_functions import transverse_reswall_wf


[docs] class WakeType(Enum): """Enum class for wake type""" FILE = 1 #: Import from file TABLE = 2 #: Provide vectors RESONATOR = 3 #: Analytical resonator RESWALL = 4 #: Analytical resistive wall
[docs] class WakeComponent(Enum): """Enum class for wake component""" DX = 1 #: Dipole X DY = 2 #: Dipole Y QX = 3 #: Quadrupole X QY = 4 #: Quadrupole Y Z = 5 #: Longitudinal
# noinspection PyPep8Naming
[docs] class Wake(object): # noinspection PyUnresolvedReferences """Class to generate a wake object The wake object is defined by its srange, specified at initialization, and one or several components corresponding to transverse dipoles or quadrupoles, or longitudinal wakes. The ``srange`` is common to all components and cannot be changed once initialized, all added component are resampled to the ``srange``. Parameters: srange: vector of s position where to sample the wake Example: >>> # Build the range >>> srange = Wake.build_srange(-0.36, 0.36, 1.0e-5, 1.0e-2, ... ring.circumference, ring.circumference) >>> # Create the Wake object >>> wake = Wake(srange) >>> # Build a resonator and add it to the Wake >>> freq = 10e9 >>> Q = 1 >>> Rs = 1e4 >>> wake.add(WakeType.RESONATOR, WakeComponent.Z, freq, Q, Rs, ... ring.beta) See :py:meth:`Wake.long_resonator` for a simple way of creatong the same :py:class:`Wake` Components may be retrieved using :code:`wake.DX` for example """ def __init__(self, srange): assert len(srange) == len(numpy.unique(srange)), \ "srange must contain unique values" self._srange = srange self.components = {WakeComponent.DX: None, WakeComponent.DY: None, WakeComponent.QX: None, WakeComponent.QY: None, WakeComponent.Z: None} @property def srange(self): return self._srange @property def DX(self): """Dipole X component""" return self.components[WakeComponent.DX] @property def DY(self): """Dipole Y component""" return self.components[WakeComponent.DY] @property def QX(self): """Quadrupole X component""" return self.components[WakeComponent.QX] @property def QY(self): """Quadrupole Y component""" return self.components[WakeComponent.QY] @property def Z(self): """Longitudinal component""" return self.components[WakeComponent.Z]
[docs] def add(self, wtype: WakeType, wcomp: WakeComponent, *args, **kwargs): """Add a component to a :py:class:`.Wake` Parameters: wtype: Wake type wcomp: Wake component *args: parameters for the selected component """ if wtype is WakeType.FILE: w = self._readwakefile(*args, **kwargs) elif wtype is WakeType.TABLE: w = self._resample(*args) elif wtype is WakeType.RESONATOR: w = self._resonator(wcomp, *args, **kwargs) elif wtype is WakeType.RESWALL: w = self._reswall(wcomp, *args, **kwargs) else: raise AtError('Invalid WakeType: {}'.format(wtype)) if self.components[wcomp] is None: self.components[wcomp] = w else: self.components[wcomp] += w
def _resample(self, s, w): if self._srange[0] < s[0] or self._srange[-1] < s[-1]: warnings.warn(AtWarning('Input wake is smaller ' 'than desired Wake() range. ' 'Filling with zeros.\n')) fint = interp1d(s, w, bounds_error=False, fill_value=0) wint = fint(self._srange) return wint def _readwakefile(self, filename, scol=0, wcol=1, sfact=1, wfact=1, delimiter=None, skiprows=0): s, w = numpy.loadtxt(filename, delimiter=delimiter, unpack=True, usecols=(scol, wcol), skiprows=skiprows) s *= sfact w *= wfact return self._resample(s, w) def _resonator(self, wcomp, frequency, qfactor, rshunt, beta, yokoya_factor=1): if numpy.amin(self._srange) < 0: raise ValueError(""" Provided srange has negative values. This is not allowed for the resonator wake function. Please correct. """) # It is needed to have a point at 0 and 1e-24 to sample properly # the discontinuity in the longitudinal plane self._srange = numpy.unique(numpy.concatenate(([0.0, 1e-24], self._srange))) if wcomp is WakeComponent.Z: return long_resonator_wf(self._srange, frequency, qfactor, rshunt, beta) elif isinstance(wcomp, WakeComponent): return transverse_resonator_wf(self._srange, frequency, qfactor, rshunt, yokoya_factor, beta) else: raise AtError('Invalid WakeComponent: {}'.format(wcomp)) def _reswall(self, wcomp, length, rvac, conduct, beta, yokoya_factor=1): if numpy.amin(self._srange) < 0: raise ValueError(""" Provided srange has negative values. This is not allowed for the resistive wall wake function. Please correct. """) # It is needed to have a point at 0 and 1e-24 to sample properly # the discontinuity in the longitudinal plane (if combining with # longres) self._srange = numpy.unique(numpy.concatenate(([0.0, 1e-24], self._srange))) if wcomp is WakeComponent.Z: raise AtError('Resitive wall not available ' 'for WakeComponent: {}'.format(wcomp)) elif isinstance(wcomp, WakeComponent): return transverse_reswall_wf(self._srange, yokoya_factor, length, rvac, conduct, beta) else: raise AtError('Invalid WakeComponent: {}'.format(wcomp))
[docs] @staticmethod def resonator(srange, wakecomp, frequency, qfactor, rshunt, beta: float, yokoya_factor=1, nelems: int = 1): r"""Factory method to build a resonator wake object Parameters: srange: vector of s position where to sample the wake wakecomp: Wake component frequency: Resonator frequency [Hz] qfactor: Q factor rshunt: Shunt impedance, [:math:`\Omega`] for longitudinal, [:math:`\Omega/m`] for transverse beta: Relativistic :math:`\beta` yokoya_factor: Yokoya factor nelems: Number of resonators Several resonators can be built in one step by setting ``nelems``> 1 and giving array parameters """ wake = Wake(srange) try: wakecomp = numpy.broadcast_to(wakecomp, (nelems, )) frequency = numpy.broadcast_to(frequency, (nelems, )) qfactor = numpy.broadcast_to(qfactor, (nelems, )) rshunt = numpy.broadcast_to(rshunt, (nelems, )) yokoya_factor = numpy.broadcast_to(yokoya_factor, (nelems, )) except ValueError: raise AtError('Wake object inputs should be either scalars ' 'or with shape (len(wakecomp), )') for wc, fr, qf, rs, yk in zip(wakecomp, frequency, qfactor, rshunt, yokoya_factor): wake.add(WakeType.RESONATOR, wc, fr, qf, rs, beta, yokoya_factor=yk) return wake
[docs] @staticmethod def long_resonator(srange, frequency, qfactor, rshunt, beta, nelems=1): # noinspection PyUnresolvedReferences r"""Factory method to build a longitudinal resonator wake object Parameters: srange: vector of s position where to sample the wake frequency: Resonator frequency [Hz] qfactor: Q factor rshunt: Shunt impedance, [:math:`\Omega`] for longitudinal, [:math:`\Omega/m`] for transverse beta: Relativistic :math:`\beta` nelems: Number of resonators Several resonators can be built in one step by setting ``nelems``> 1 and giving array parameters Example: >>> # Build the range >>> srange = Wake.build_srange(-0.36, 0.36, 1.0e-5, 1.0e-2, ... ring.circumference, ring.circumference) >>> # Create the resonator >>> freq = 10e9 >>> Q = 1 >>> Rs = 1e4 >>> wake = Wake.long_resonator(srange, freq, Q, Rs, ring.beta) """ return Wake.resonator(srange, WakeComponent.Z, frequency, qfactor, rshunt, beta, nelems=nelems)
[docs] @staticmethod def resistive_wall(srange, wakecomp: WakeComponent, length, rvac, conduct, beta: float, yokoya_factor=1, nelems: int = 1): r"""Factory method to build a resistive wall wake object Parameters: srange: vector of s position where to sample the wake wakecomp: Wake component length: rvac: conduct: beta: Relativistic :math:`\beta` yokoya_factor: Yokoya factor nelems: """ wake = Wake(srange) try: wakecomp = numpy.broadcast_to(wakecomp, (nelems, )) length = numpy.broadcast_to(length, (nelems, )) rvac = numpy.broadcast_to(rvac, (nelems, )) conduct = numpy.broadcast_to(conduct, (nelems, )) yokoya_factor = numpy.broadcast_to(yokoya_factor, (nelems, )) except ValueError: raise AtError('Wake object inputs should be either scalars ' 'or with shape (len(wakecomp), )') for wc, le, rv, co, yk in zip(wakecomp, length, rvac, conduct, yokoya_factor): wake.add(WakeType.RESWALL, wc, le, rv, co, beta, yokoya_factor=yk) return wake
[docs] @staticmethod def build_srange(start: float, bunch_ext: float, short_step: float, long_step: float, bunch_interval: float, totallength: float): """Function to build the wake table s column. This is not the slicing but the look-up table, however it generates data where bunches are located to avoid using too much memory to store the table. Parameters: start: starting s-coordinate of the table (can be negative for wake potential) bunch_ext: maximum bunch extension, function generates data at +/- bunch_ext around the bucket center short_step: step size for the short range wake table long_step: step size for the long range wake table bunch_interval: minimum bunch interval. Data will be generated for each ``bunch_interval`` step totallength: total length of the wake table, has to contain the full bunch extension Returns: srange: vector of s position where to sample the wake """ srange = numpy.arange(start, bunch_ext, short_step) rangel = numpy.arange(-bunch_ext, bunch_ext, long_step) nbunch = int((totallength-bunch_ext)/bunch_interval) for i in range(nbunch): srange = numpy.concatenate((srange, rangel+bunch_interval*(i+1))) return numpy.unique(srange)