from math import pi, sqrt, asin, cos
import numpy
from numpy import nan
from typing import Optional
from .radiation import get_radiation_integrals, ohmi_envelope
from .energy_loss import get_energy_loss, get_timelag_fromU0
from ..lattice import Lattice, Orbit
from ..constants import clight, Cgamma, Cq
__all__ = ['RingParameters', 'radiation_parameters', 'envelope_parameters']
[docs]class RingParameters(object):
"""Class for pretty printing the ring properties"""
_props = {
'tunes': ' Frac. tunes: {0}',
'tunes6': ' Frac. tunes (6D motion): {0}',
'fulltunes': ' Tunes: {0}',
'chromaticities': ' Chromaticities: {0}',
'alphac': ' Momentum compact. factor: {0:e}',
'etac': ' Slip factor: {0:e}',
'E0': ' Energy: {0:e} eV',
'U0': ' Energy loss / turn: {0:e} eV',
'i1': ' Radiation integrals - I1: {0} m',
'i2': ' I2: {0} m^-1',
'i3': ' I3: {0} m^-2',
'i4': ' I4: {0} m^-1',
'i5': ' I5: {0} m^-1',
'emittances': ' Mode emittances: {0}',
'J': 'Damping partition numbers: {0}',
'Tau': ' Damping times: {0} s',
'sigma_e': ' Energy spread: {0:g}',
'sigma_l': ' Bunch length: {0:g} m',
'voltage': ' Cavities voltage: {0} V',
'phi_s': ' Synchrotron phase: {0:g} rd',
'f_s': ' Synchrotron frequency: {0:g} Hz'
}
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
def __str__(self):
vrs = vars(self).copy()
vals = [(self._props[k], vrs.pop(k, None)) for k in self._props]
# Predefined attributes
lines = [f.format(v) for f, v in vals if v is not None]
# Other attributes
lines += ['{0:>25}: {1}'.format(k, getattr(self, k)) for k in vrs]
return '\n'.join(lines)
# noinspection PyPep8Naming
[docs]def radiation_parameters(ring: Lattice, dp: Optional[float] = None,
params: Optional[RingParameters] = None,
**kwargs):
r"""Compute ring parameters from the radiation integrals
Valid for uncoupled lattices with no RF cavity or radiating element.
Parameters:
ring: Lattice description.
dp: Momentum deviation.
params: :py:class:`.RingParameters` object to be updated.
Default: create a new one
Keyword Args:
dct (float): Path lengthening. If specified, ``dp`` is
ignored and the off-momentum is deduced from the path lengthening.
method (Callable): Method for linear optics:
:py:obj:`~.linear.linopt2`: no longitudinal motion, no H/V coupling,
:py:obj:`~.linear.linopt6` (default): with or without longitudinal
motion, normal mode analysis
orbit (Orbit): Avoids looking for the closed orbit if is
already known ((6,) array)
XYStep (float): Step size.
Default: :py:data:`DConstant.XYStep <.DConstant>`
DPStep (float): Momentum step size.
Default: :py:data:`DConstant.DPStep <.DConstant>`
Returns:
params: :py:class:`.RingParameters` object.
**params** is a :py:class:`.RingParameters` object with the following
attributes:
================== ========================================
**tunes** (3,) fractional (H, V, Long.) tunes
**fulltunes** (3,) full tunes
**chromaticities** (2,) H, V Chromaticities
**alphac** Momentum compaction factor
**etac** Frequency slip factor
**E0** Energy [eV]
**U0** Energy loss / turn [eV]
**i1** Radiation integrals - :math:`I_1 \quad [m]`
**i2** :math:`I_2 \quad [m^{-1}]`
**i3** :math:`I_3 \quad [m^{-2}]`
**i4** :math:`I_4 \quad [m^{-1}]`
**i5** :math:`I_5 \quad [m^{-1}]`
**emittances** (3,) Mode emittances
**J** (3,) Damping partition numbers
**Tau** (3,) Damping times [s]
**sigma_e** Energy spread
**sigma_l** Bunch length [m]
**voltage** Total accelerating voltage [V]
**phi_s** Synchrotron phase [rad]
**f_s** Synchrotron frequency [Hz]
================== ========================================
"""
rp = RingParameters() if params is None else params
_, ringdata, twiss = ring.get_optics(refpts=range(len(ring) + 1), dp=dp,
get_chrom=True, **kwargs)
rp.chromaticities = ringdata.chromaticity * ring.periodicity
integs = get_radiation_integrals(ring, dp=dp, twiss=twiss)
rp.i1, rp.i2, rp.i3, rp.i4, rp.i5 = numpy.array(integs) * ring.periodicity
circumference = ring.circumference
voltage = ring.rf_voltage
E0 = ring.energy
gamma = ring.gamma
gamma2 = gamma * gamma
beta = sqrt(1.0 - 1.0/gamma2)
U0 = Cgamma / 2.0 / pi * E0**4 * rp.i2
Jx = 1.0 - rp.i4/rp.i2
Jz = 1.0
Je = 2.0 + rp.i4/rp.i2
damping_partition_numbers = numpy.array([Jx, Jz, Je])
revolution_period = circumference / clight / beta
ct = 2.0 * E0 / U0 * revolution_period
rp.E0 = E0
rp.U0 = U0
emitx = Cq * gamma2 * rp.i5 / Jx / rp.i2
alphac = rp.i1 / circumference
etac = 1.0/gamma2 - alphac
rp.phi_s = (pi - asin(U0 / voltage)) if U0 <= voltage else nan
nus = sqrt(abs(etac * ring.harmonic_number *
voltage * cos(rp.phi_s) / E0 / 2.0 / pi)) / beta
rp.voltage = voltage
rp.f_s = nus / revolution_period
rp.Tau = ct / damping_partition_numbers
rp.J = damping_partition_numbers
rp.sigma_e = sqrt(Cq * gamma2 * rp.i3 / Je / rp.i2)
rp.sigma_l = beta * abs(etac) * circumference / 2.0 / pi / nus * rp.sigma_e
rp.emittances = numpy.array([emitx, nan, rp.sigma_e*rp.sigma_l])
ringtunes, _ = numpy.modf(ring.periodicity * ringdata.tune)
if len(ringtunes) < 3:
rp.tunes = numpy.concatenate((ringtunes, (nus,)))
else:
rp.tunes6 = ringtunes
rp.fulltunes = ring.periodicity * twiss[-1].mu / 2.0 / pi
rp.alphac = alphac
rp.etac = etac
return rp
# noinspection PyPep8Naming
[docs]def envelope_parameters(ring: Lattice,
params: Optional[RingParameters] = None,
orbit: Orbit = None,
keep_lattice: bool = False) -> RingParameters:
r"""Compute ring parameters from ohmi_envelope
Parameters:
ring: Lattice description.
params: :py:class:`.RingParameters` object to be updated.
Default: create a new one
orbit: Avoids looking for the closed orbit if it is
already known ((6,) array)
keep_lattice: Assume no lattice change since the
previous tracking.
Returns:
params: :py:class:`.RingParameters` object.
**params** is a :py:class:`.RingParameters` object with the following
attributes:
================== ========================================
**tunes6** (3,) fractional (H, V, Long.) tunes (6D motion)
**emittances** (3,) Mode emittances
**J** (3,) Damping partition numbers
**Tau** (3,) Damping times [s]
**sigma_e** Energy spread
**sigma_l** Bunch length [m]
**voltage** Total accelerating voltage [V]
**phi_s** Synchrotron phase [rad]
**f_s** Synchrotron frequency [Hz]
================== ========================================
"""
rp = RingParameters() if params is None else params
emit0, beamdata, emit = ohmi_envelope(ring, orbit=orbit,
keep_lattice=keep_lattice)
voltage = ring.rf_voltage
rp.E0 = ring.energy
rp.U0 = get_energy_loss(ring)
rev_freq = ring.revolution_frequency
rp.Tau = 1.0 / rev_freq / beamdata.damping_rates / ring.periodicity
alpha = 1.0 / rp.Tau
rp.J = 4.0 * alpha / numpy.sum(alpha)
rp.tunes6, _ = numpy.modf(ring.periodicity * beamdata.tunes)
rp.phi_s = pi - numpy.arcsin(rp.U0 / voltage)
rp.voltage = voltage
rp.f_s = rp.tunes6[2] * rev_freq
rp.emittances = beamdata.mode_emittances
rp.sigma_e = sqrt(emit0.r66[4, 4])
rp.sigma_l = sqrt(emit0.r66[5, 5])
return rp
Lattice.radiation_parameters = radiation_parameters
Lattice.envelope_parameters = envelope_parameters