Source code for at.physics.amat

"""A-matrix construction"""
import numpy
from scipy.linalg import block_diag, eig, inv, solve
from math import pi
from at.lattice import AtError

__all__ = ['a_matrix', 'amat', 'jmat', 'jmatswap',
           'get_tunes_damp', 'get_mode_matrices', 'symplectify']

_i2 = numpy.array([[-1.j, -1.], [1., 1.j]])

# Prepare symplectic identity matrix
_j2 = numpy.array([[0., 1.], [-1., 0.]])
_jm = [_j2, block_diag(_j2, _j2), block_diag(_j2, _j2, _j2)]
_jmswap = [_j2, block_diag(_j2, _j2), block_diag(_j2, _j2, _j2.T)]

_vxyz = [_i2, block_diag(_i2, _i2), block_diag(_i2, _i2, _i2)]
_submat = [slice(0, 2), slice(2, 4), slice(4, 6)]


[docs]def jmat(ind: int): """antisymetric block diagonal matrix [[0, 1][-1, 0]] Parameters: ind: Matrix dimension: 1, 2 or 3 Returns: S: Block diagonal matrix, (2, 2) or (4, 4) or (6, 6) """ return _jm[ind - 1]
[docs]def jmatswap(ind: int): """Modified antisymetric block diagonal matrix to deal with the swap of the longitudinal coordinates """ return _jmswap[ind - 1]
# noinspection PyPep8Naming
[docs]def a_matrix(M): r"""Find the :math:`\mathbf{A}` matrix from one turn map :math:`\mathbf{M}` :math:`\mathbf{A}` represents a change of referential which converts the one-turn transfer matrix :math:`\mathbf{M}` into a set of rotations: .. math:: \mathbf{M} = \mathbf{A} \cdot \mathbf{R} \cdot \mathbf{A}^{-1} with :math:`\mathbf{R}` A block-diagonal matrix: .. math:: \mathbf{R}=\begin{pmatrix}rot_1 & 0 & 0 \\ 0 & rot_2 & 0 \\ 0 & 0 & rot_3\end{pmatrix} \text{, and } rot_i = \begin{pmatrix}\cos{\mu_i} & \sin{\mu_i} \\ -\sin{\mu_i} & cos{\mu_i}\end{pmatrix} With radiation damping, the diagonal blocks are instead damped rotations: .. math:: rot_i = \begin{pmatrix}\exp{(-\alpha_i}) & 0 \\ 0 & \exp{(-\alpha_i)} \end{pmatrix} \cdot \begin{pmatrix}\cos{\mu_i} & \sin{\mu_i} \\ -\sin{\mu_i} & cos{\mu_i}\end{pmatrix} The order of diagonal blocks it set so that it is close to the order of x,y,z. Parameters: M: (m, m) transfer matrix for 1 turn m, the dimension of :math:`\mathbf{M}`, may be 2 (single plane), 4 (betatron motion) or 6 (full motion) Returns: A: (m, m) A-matrix eigval: (m/2,) Vector of Eigen values of T References: **[1]** Etienne Forest, Phys. Rev. E 58, 2481 – Published 1 August 1998 """ nv = M.shape[0] dms = int(nv / 2) jmt = jmatswap(dms) select = numpy.arange(0, nv, 2) rbase = numpy.stack((select, select), axis=1).flatten() # noinspection PyTupleAssignmentBalance lmbd, vv = eig(M) # Compute the norms vp = vv.conj().T @ jmt n = -0.5j * numpy.sum(vp.T * vv, axis=0) if any(abs(n) < 1.0E-12): raise AtError('Unstable ring') # Move positive before negatives order = rbase + (n < 0) vv = vv[:, order] n = n[order] lmbd = lmbd[order] # Normalize vectors vn = vv / numpy.sqrt(abs(n)).reshape((1, nv)) # find the vectors that project most onto x,y,z, and reorder # nn will have structure # n1x n1y n1z # n2x n2y n2z # n3x n3y n3z nn = 0.5 * abs(numpy.sqrt(-1.j * vn.conj().T @ jmt @ _vxyz[dms - 1])) rows = list(select) order = [] for ixz in select: ind = numpy.argmax(nn[rows, ixz]) order.append(rows[ind]) del rows[ind] v_ordered = vn[:, order] lmbd = lmbd[order] aa = numpy.vstack((numpy.real(v_ordered), numpy.imag(v_ordered))).reshape( (nv, nv), order='F') return aa, lmbd
# noinspection PyPep8Naming
[docs]def amat(M): """Find the A matrix from one turn map matrix T Provided for backward compatibility, see :py:func:`a_matrix` Parameters: M: (m, m) transfer matrix for 1 turn Returns: A: (m, m) A-matrix """ aa, _ = a_matrix(M) return aa
# noinspection PyPep8Naming
[docs]def symplectify(M): """Makes A matrix more symplectic following the Healy algorithm described by MacKay Parameters: M: Almost symplectic matrix Returns: MS: Symplectic matrix References: **[1]** `W.W.MacKay, Comment on Healy's symplectification algorithm, Proceedings of EPAC 2006 <https://accelconf.web.cern.ch/e06/PAPERS/WEPCH152.PDF>`_ """ nv = M.shape[0] S = jmat(nv // 2) I = numpy.identity(nv) V = S @ (I - M) @ inv(I + M) # V should be almost symmetric. Replace with symmetrized version. W = (V + V.T) / 2 # Now reconstruct M from W SW = S @ W MS = (I + SW) @ inv(I - SW) return MS
# noinspection PyPep8Naming
[docs]def get_mode_matrices(A): """Derives the R-matrices from the A-matrix Parameters: A: A-matrix Returns: R: References: **[1]** Andrzej Wolski, Phys. Rev. ST Accel. Beams 9, 024001 – Published 3 February 2006 """ def mul2(slc): return A[:, slc] @ tt[slc, slc] dms = A.shape[0] // 2 # Rk = A * Ik * A.T Only for symplectic # modelist = [numpy.dot(A[:, sl], A.T[sl, :]) for sl in _submat[:dms]] # Rk = A * S * Ik * inv(A) * S.T Even for non-symplectic ss = jmat(dms) tt = jmatswap(dms) a_s = numpy.concatenate([mul2(slc) for slc in _submat[:dms]], axis=1) inva = solve(A, ss.T) modelist = [a_s[:, sl] @ inva[sl, :] for sl in _submat[:dms]] return numpy.stack(modelist, axis=0)
# noinspection PyPep8Naming
[docs]def get_tunes_damp(M, R=None): r"""Computes the mode emittances, tunes and damping times Parameters: M: (m, m) transfer matrix for 1 turn R: (m, m) beam matrix (optional), allows computing the mode emittances m, the dimension of :math:`\mathbf{M}`, may be 2 (single plane), 4 (betatron motion) or 6 (full motion) Returns: V: record array with the following fields: **tunes** (m/2,) tunes of the m/2 normal modes **damping_rates** (m/2,) damping rates of the m/2 normal modes **mode_matrices** (m/2, m, m) the R-matrices of the m/2 normal modes **mode_emittances** Only if R is specified: (m/2,) emittance of each of the m/2 normal modes """ nv = M.shape[0] dms = int(nv / 2) A, vps = a_matrix(M) tunes = numpy.mod(numpy.angle(vps) / 2.0 / pi, 1.0) damping_rates = -numpy.log(numpy.absolute(vps)) if R is None: return numpy.rec.fromarrays( (numpy.array(tunes), numpy.array(damping_rates), numpy.array(get_mode_matrices(A))), dtype=[('tunes', numpy.float64, (dms,)), ('damping_rates', numpy.float64, (dms,)), ('mode_matrices', numpy.float64, (dms, nv, nv))] ) else: jmt = jmat(dms) rdiag = numpy.diag(A.T @ jmt @ R @ jmt @ A) mode_emit = -0.5 * (rdiag[0:nv:2] + rdiag[1:nv:2]) return numpy.rec.fromarrays( (numpy.array(tunes), numpy.array(damping_rates), numpy.array(get_mode_matrices(A)), mode_emit), dtype=[('tunes', numpy.float64, (dms,)), ('damping_rates', numpy.float64, (dms,)), ('mode_matrices', numpy.float64, (dms, nv, nv)), ('mode_emittances', numpy.float64, (dms,))] )