"""
Functions relating to particle generation
"""
import numpy as np
from numpy.linalg import cholesky, LinAlgError
from warnings import warn
from ..lattice import AtError, AtWarning, Lattice, Orbit, random
__all__ = ['beam', 'sigma_matrix']
# noinspection PyPep8Naming
[docs]
def sigma_matrix(ring: Lattice = None, **kwargs):
r"""
Calculate the correlation matrix :math:`\Sigma` to be used for particle
generation
Parameters:
ring: Lattice object or list of twiss parameters.
Keyword Args:
beam: Particle coordinates
twiss_in: Data structure containing input twiss parameters.
betax (float): Input horizontal beta function [m]
alphax (float): Input horizontal alpha function [m]
emitx (float): Horizontal emittance [m.rad]
betay (float): Input vertical beta function [m]
alphay (float): Input vertical alpha function [m]
emity (float): Vertical emittance [m.rad]
blength (float): One sigma bunch length [m]
espread (float): One sigma energy spread [dp/p]
Returns:
sigma_matrix: (6, 6) :math:`\Sigma`-matrix
*beam* is a (6, nparticles) coordinates array of a particle population.
If provided, all other parameters are ignored.
If *twiss_in* is provided, its *R* or *alpha* and *beta* fields are used,
*emitx* and *emity* must be provided.
If *ring* is provided:
* for a 6d lattice, :py:func:`.ohmi_envelope` is called to compute the
equilibrium emittances. *emitx*, *emity* may overload these values.
* for a 4d lattice, *emitx*, *emity* must be provided.
Otherwise, *alphax*, *betax*, *emitx*, *alphay*, *betay* and *emity*
must be provided. The result is an uncoupled :math:`\Sigma`-matrix
If *espread* and *blength* are not specified, the results is a
monochromatic beam.
"""
def require(mess, *keys):
miss = [key for key in keys if key not in kwargs]
if len(miss) == 0:
return [kwargs.pop(key) for key in keys]
else:
raise AtError('{0}: missing {1}'.format(mess, ', '.join(miss)))
def ignore(mess):
ok = kwargs.keys()
if any(ok):
warn(AtWarning('{0}: {1} ignored'.format(mess, ', '.join(ok))))
def r_s():
rs = np.zeros((2, 2))
if any(('espread' in kwargs, 'blength' in kwargs)):
blength, espread = require(
'Both "blength" and "espread" are required',
'blength', 'espread')
ems = blength * espread
if ems > 0.0:
rs = np.array([[espread / blength, 0], [0, blength / espread]])
else:
ems = None
return ems, rs
def r_4d(ax, ay, bx, by, rs):
r6 = np.zeros((3, 6, 6))
r6[0, :2, :2] = np.array([[bx, -ax], [-ax, (1+ax**2)/bx]])
r6[1, 2:4, 2:4] = np.array([[by, -ay], [-ay, (1+ay**2)/by]])
r6[2, 4:, 4:] = rs
return r6
def r_expand(rmat, rs):
if rmat.shape[0] < 3:
r6 = np.zeros((3, 6, 6))
r6[:2, :4, :4] = rmat
r6[2, 4:, 4:] = rs
else:
r6 = rmat
return r6
def sigma_from_rmat(rmat, emit, embl):
if emit is None:
emx, emy = require('Emittances must be provided', 'emitx', 'emity')
ems = embl
else:
emx = kwargs.pop('emitx', emit[0])
emy = kwargs.pop('emity', emit[1])
ems = emit[2] if embl is None else embl
sigm = emx * rmat[0, :, :] + emy * rmat[1, :, :]
if ems is None:
warn(AtWarning('Monochromatic beam: no energy spread'))
else:
sigm += ems * rmat[2, :, :]
return sigm
if ring is not None:
kwargs['ring'] = ring
if 'beam' in kwargs:
message = "'beam' is provided"
beam = kwargs.pop('beam')
orbit = kwargs.pop('orbit', None)
if orbit is None:
orbit = np.mean(beam, axis=1)
beam -= orbit.reshape((6, 1))
sigmat = (beam @ beam.T) / beam.shape[1]
elif 'twiss_in' in kwargs:
twin = kwargs.pop('twiss_in')
message = "'twiss_in' is provided"
em56, r56 = r_s()
try:
Rin = twin['R']
except (ValueError, KeyError): # record arrays throw ValueError !
Rm = r_4d(twin['alpha'][0], twin['alpha'][1],
twin['beta'][0], twin['beta'][1], r56)
else:
Rm = r_expand(Rin, r56)
sigmat = sigma_from_rmat(Rm, None, em56)
elif 'ring' in kwargs:
ring = kwargs.pop('ring')
message = "'ring' is provided"
em56, r56 = r_s()
el0, _, _ = ring.linopt6(orbit=kwargs.pop('orbit', None))
orbit = el0.closed_orbit
Rm = r_expand(el0['R'], r56)
if ring.is_6d:
_, beam0, _ = ring.ohmi_envelope(orbit=orbit)
sigmat = sigma_from_rmat(Rm, beam0.mode_emittances, em56)
else:
sigmat = sigma_from_rmat(Rm, None, em56)
else:
message = "Uncoupled beam"
alphax, alphay, betax, betay = require(
'Neither "twiss_in" nor "ring" is provided',
'alphax', 'alphay', 'betax', 'betay')
# orbit = kwargs.pop('orbit', np.zeros(6))
em56, r56 = r_s()
Rm = r_4d(alphax, alphay, betax, betay, r56)
sigmat = sigma_from_rmat(Rm, None, em56)
ignore(message)
return sigmat
[docs]
def beam(nparts: int, sigma, orbit: Orbit = None):
r"""
Generates an array of random particles according to the given
:math:`\Sigma`-matrix
Parameters:
nparts: Number of particles
sigma: :math:`\Sigma`-matrix as calculated by
:py:func:`sigma_matrix`
orbit: An orbit can be provided to give a center of
mass offset to the distribution
Returns:
particle_dist: a (6, *nparts*) matrix of coordinates
"""
def _get_single_plane(slc):
try:
return cholesky(sigma[slc, slc])
except LinAlgError:
return np.zeros((2, 2))
v = random.thread.normal(size=(sigma.shape[1], nparts))
try:
# Try full 6x6 matrix
lmat = cholesky(sigma)
print("h, v, delta")
except LinAlgError:
lmat = np.zeros((6, 6))
try:
# Try x-y 4x4 matrix
sel = range(4)
idx = np.ix_(sel, sel)
lmat[idx] = cholesky(sigma[idx])
lmat[4:, 4:] = _get_single_plane(slice(4, 6))
print("h, v")
except LinAlgError:
try:
# Try x-delta 4x4 matrix
sel = [0, 1, 4, 5]
idx = np.ix_(sel, sel)
lmat[idx] = cholesky(sigma[idx])
lmat[2:4, 2:4] = _get_single_plane(slice(2, 4))
print("h, delta")
except LinAlgError:
# Uncoupled
lmat[:2, :2] = _get_single_plane(slice(2))
lmat[2:4, 2:4] = _get_single_plane(slice(2, 4))
lmat[4:, 4:] = _get_single_plane(slice(4, 6))
print("uncoupled")
particle_dist = np.asfortranarray(lmat @ v)
if orbit is not None:
particle_dist += orbit.reshape((6, 1))
return particle_dist