Source code for at.latticetools.emit_observables

__all__ = ["LocalEmittanceObservable"]

from collections.abc import Callable
from functools import partial
from typing import ClassVar

import numpy as np

from ..lattice import AxisDef, plane_, axis_
from ..lattice import Refpts
from .observables import ElementObservable, Need
# noinspection PyProtectedMember
from .observables import _all_rows, _record_access, _fun_access, _subscript


def _sigma4(_, index, data, **kwargs):
    """Betatron beam size: square root of diagonal terms of r44."""
    idx = index[1]
    return np.sqrt(data.r44[_all_rows((idx, idx))])


def _sigma6(_, index, data, **kwargs):
    """Beam size: square root of diagonal terms of r66."""
    idx = index[1]
    return np.sqrt(data.r66[_all_rows((idx, idx))])


_opdata = {"sigma4": _sigma4, "sigma6": _sigma6}


[docs] class LocalEmittanceObservable(ElementObservable): """Observe emittance-related parameters.""" # Class attributes _pinfo: ClassVar[dict] = { "r66": (r"$R66_{{{plane}}}$", "R", _subscript), "r44": (r"$R44_{{{plane}}}$", "R", _subscript), "emitXY": ( r"$\epsilon_{{{plane}}}$", "Emittance [m]", partial(plane_, key="label"), ), "emitXYZ": ( r"$\epsilon_{{{plane}}}$", "Emittance [m]", partial(plane_, key="label"), ), "m66": (r"$\mathrm{{T}}_{{{plane}}}$", "T [m]", _subscript), "orbit6": (r"${{{plane}}}_{{co}}$", "closed orbit", partial(axis_, key="code")), "sigma4": (r"$\sigma_{{{plane}}}$", "sigma", partial(axis_, key="label")), "sigma6": (r"$\sigma_{{{plane}}}$", "sigma", partial(axis_, key="label")), } def __init__( self, refpts: Refpts, param: str | Callable, *, plane: AxisDef = Ellipsis, axis: AxisDef = Ellipsis, name: str | None = None, summary: bool = False, label: str | None = None, **eval_kw, ): # noinspection PyUnresolvedReferences r"""Args: refpts: Observation points. See ":ref:`Selecting elements in a lattice <refpts>`" param: :ref:`Emittance parameter name <localemit_param>` or :ref:`user-defined evaluation function <localemit_eval>` axis: Index in the parameter array. If :py:obj:`Ellipsis`, the whole array is specified plane: Index in the parameter array for *emitXY* and *emitXYZ* parameters. If :py:obj:`Ellipsis`, the whole array is specified name: Observable name. If :py:obj:`None`, an explicit name will be generated. Keyword Args: summary: Set to :py:obj:`True` if the user-defined evaluation function returns a single item (see below) postfun: Post-processing function. It can be any numpy ufunc or a function name in {"real", "imag", "abs", "angle", "log", "exp", "sqrt"}. statfun: Statistics post-processing function. it can be a numpy function or a function name in {"mean", "std", "var", "min", "max"}. Example: :pycode:`statfun=numpy.mean`. target: Target value for a constraint. If :py:obj:`None` (default), the residual will always be zero. weight: Weight factor: the residual is :pycode:`((value-target)/weight)**2` bounds: Tuple of lower and upper bounds. The parameter is constrained in the interval [*target*\ +\ *low_bound* *target*\ +\ *up_bound*] .. rubric:: Evaluation keywords These values must be provided to the :py:meth:`~.ObservableList.evaluate` method. Default values may be given at instantiation. * **ring**: Lattice description, * **dp**: Momentum deviation. Defaults to :py:obj:`None`, * **dct**: Path lengthening. Defaults to :py:obj:`None`, * **df**: Deviation from the nominal RF frequency. Defaults to :py:obj:`None`, * **orbit**: Initial orbit. Avoids looking for the closed orbit if it is already known, .. rubric:: Shape of the value If the requested attribute has shape :pycode:`shp`, then *value* has shape :pycode:`(nrefs,) + shp` with :pycode:`nrefs` number of reference points: a :pycode:`nrefs` dimension is prepended to the shape of the attribute, **even if nrefs == 1**. The *target*, *weight* and *bounds* inputs must be broadcastable to the shape of *value*. For instance, a *target* with shape :pycode:`shp` will automatically broadcast and apply to all reference points. .. _localemit_param: .. rubric:: Emittance parameter names In addition to :py:func:`.ohmi_envelope` parameter names, LocalEmittanceObservable adds the *sigma* parameter: =========== =================================================== **r66** (6, 6) equilibrium envelope matrix :math:`\Sigma` **r44** (4, 4) betatron emittance matrix (dp = 0) **m66** (6, 6) transfer matrix from the start of the ring **orbit6** (6,) closed orbit **emitXY** (2,) betatron emittance projected on xxp and yyp **emitXYZ** (3,) 6x6 emittance projected on xxp, yyp, ldp **sigma6** (6,) standard deviation of the beam (square root of the diagonal terms of *r66*) **sigma4** (6,) standard deviation of the monochromatic beam (square root of the diagonal terms of *r44*) =========== =================================================== .. _localemit_eval: .. rubric:: User-defined evaluation function The observable value is computed as: :pycode:`value = fun(emit, **eval_kw)` - *emit* is the output of :py:func:`.ohmi_envelope`, evaluated at the *refpts* of the observable, - *eval_kw* are the keyword arguments provided to the observable constructor, to the constructor of the enclosing :py:class:`.ObservableList` and to the :py:meth:`~.ObservableList.evaluate` method, - *value* is the value of the Observable and must have one line per refpoint. Alternatively, it may be a single line, but then the *summary* keyword must be set to :py:obj:`True`, Examples: >>> obs = LocalEmittanceObservable(at.Monitor, "sigma6") Observe the beam standard deviation in all 6 axes at Monitor locations >>> obs = LocalEmittanceObservable( ... at.Quadrupole, "sigma6", axis="x", statfun=np.max ... ) Observe the maximum horizontal beam size in Quadrupoles """ needs = {Need.LOCALEMIT} descr = plane_(plane) if param in {"emitXY", "emitXYZ"} else axis_(axis) name = self._set_name(name, param, descr["code"]) index = _all_rows(descr["index"]) if callable(param): if summary: fun = partial(_fun_access, param, descr["index"]) else: fun = partial(_fun_access, param, index) else: fun = partial(_opdata.get(param, _record_access), param, index) super().__init__( fun, refpts, needs=needs, name=name, summary=summary, label=self._pl_lab(param, descr["index"]), axis_label=self._ax_lab(param, descr["index"]), **eval_kw, ) if label: self.label = label