Source code for at.lattice.utils

r"""
Helper functions for working with AT lattices.

A lattice as understood by pyAT is a sequence of elements.  These functions
are useful for working with these sequences.

.. _refpts:

**Selecting elements in a lattice:**

The *refpts* argument allows functions to select locations in the lattice. The
location is defined as the entrance of the selected element. *refpts* may be:

#. an integer in the range *[-len(lattice), len(lattice)-1]*
   selecting an element according to the python indexing rules.
   As a special case, *len(lattice)* is allowed and refers to the end
   of the last element,
#. an ordered list of such integers without duplicates,
#. a numpy array of booleans of maximum length *len(lattice)+1*,
   where selected elements are :py:obj:`True`,
#. :py:obj:`None`, meaning an empty selection,
#. :py:obj:`.All`, meaning "all possible reference points": the entrance of all
   elements plus the end of the last element,
#. :py:obj:`.End`, selecting the end of the last element,
#. an element type, selecting all the elements of that type in
   the lattice, e.g. :pycode:`at.Sextupole`,
#. a string, selecting all the elements whose `FamName` attribute matches it.
   Unix shell-style wildcards are accepted, e.g. `"Q[FD]*"`,
#. a callable :pycode:`filtfunc` such that :pycode:`filtfunc(elem)`
   is :py:obj:`True` for selected elements.

"""

from __future__ import annotations

import functools
import re
from enum import Enum
from fnmatch import fnmatch
from itertools import compress
from operator import attrgetter
from typing import Union

# Necessary for type aliases in python <= 3.8 :
# from collections.abc import Callable, Sequence, Iterator
from typing import Callable, Sequence, Iterator, Type

import numpy
import numpy.typing as npt

from .elements import Element
from .exceptions import AtError

__all__ = [
    "All",
    "End",
    "BoolRefpts",
    "Uint32Refpts",
    "check_radiation",
    "check_6d",
    "set_radiation",
    "set_6d",
    "make_copy",
    "uint32_refpts",
    "bool_refpts",
    "get_uint32_index",
    "get_bool_index",
    "checkattr",
    "checktype",
    "checkname",
    "get_elements",
    "get_s_pos",
    "refpts_count",
    "refpts_iterator",
    "get_value_refpts",
    "set_value_refpts",
    "Refpts",
    "setval",
    "getval",
]


_typ1 = "None, All, End, int, bool"

_typ2 = "None, All, End, int, bool, str, type[Element], ElementFilter"


class RefptsCode(Enum):
    All = "All"
    End = "End"


ElementFilter = Callable[[Element], bool]
BoolRefpts = npt.NDArray[bool]
Uint32Refpts = npt.NDArray[numpy.uint32]
RefIndex = Union[None, int, Sequence[int], bool, Sequence[bool], RefptsCode]
Refpts = Union[Type[Element], Element, ElementFilter, str, RefIndex]


#: :py:obj:`All` is a special value to be used as *refpts*. It means
#: "all possible reference points": the entrance of all elements plus the end
#: of the last element.
All = RefptsCode.All

#: :py:obj:`End` is a special value to be used as *refpts*. It refers to the
#: end of the last element.
End = RefptsCode.End


def _chkattr(attrname: str, el):
    return hasattr(el, attrname)


def _chkattrval(attrname: str, attrvalue, el):
    try:
        v = getattr(el, attrname)
    except AttributeError:
        return False
    else:
        return v == attrvalue


def _chkpattern(pattern: str, el):
    return fnmatch(el.FamName, pattern)


def _chkregex(pattern: str, el):
    rgx = re.compile(pattern)
    return rgx.fullmatch(el.FamName)


def _chktype(eltype: type, el):
    return isinstance(el, eltype)


def _type_error(refpts, types):
    if isinstance(refpts, numpy.ndarray):
        tp = refpts.dtype.type
    else:
        tp = type(refpts)
    return TypeError(f"Invalid refpts type {tp}. Allowed types: {types}")


# setval and getval return pickleable functions: no inner, nested function
# is allowed. So nested functions are replaced be module-level callable
# class instances
class _AttrItemGetter:
    __slots__ = ["attrname", "index"]

    def __init__(self, attrname: str, index: int):
        self.attrname = attrname
        self.index = index

    def __call__(self, elem):
        return getattr(elem, self.attrname)[self.index]


[docs] def getval(attrname: str, index: int | None = None) -> Callable: """Return a callable object which fetches item *index* of attribute *attrname* of its operand. Examples: - After ``f = getval('Length')``, ``f(elem)`` returns ``elem.Length`` - After ``f = getval('PolynomB', index=1)``, ``f(elem)`` returns ``elem.PolynomB[1]`` """ if index is None: return attrgetter(attrname) else: return _AttrItemGetter(attrname, index)
class _AttrSetter: __slots__ = ["attrname"] def __init__(self, attrname: str): self.attrname = attrname def __call__(self, elem, value): setattr(elem, self.attrname, value) class _AttrItemSetter: __slots__ = ["attrname", "index"] def __init__(self, attrname: str, index: int): self.attrname = attrname self.index = index def __call__(self, elem, value): getattr(elem, self.attrname)[self.index] = value
[docs] def setval(attrname: str, index: int | None = None) -> Callable: """Return a callable object which sets the value of item *index* of attribute *attrname* of its 1st argument to it 2nd orgument. - After ``f = setval('Length')``, ``f(elem, value)`` is equivalent to ``elem.Length = value`` - After ``f = setval('PolynomB', index=1)``, ``f(elem, value)`` is equivalent to ``elem.PolynomB[1] = value`` """ if index is None: return _AttrSetter(attrname) else: return _AttrItemSetter(attrname, index)
def check_radiation(rad: bool) -> Callable: r"""Deprecated decorator for optics functions (see :py:func:`check_6d`). :meta private: """ return check_6d(rad) def set_radiation(rad: bool) -> Callable: r"""Deprecated decorator for optics functions (see :py:func:`set_6d`). :meta private: """ return set_6d(rad)
[docs] def check_6d(is_6d: bool) -> Callable: r"""Decorator for optics functions Wraps a function like :pycode:`func(ring, *args, **kwargs)` where :pycode:`ring` is a :py:class:`.Lattice` object. Raise :py:class:`.AtError` if :pycode:`ring.is_6d` is not the desired *is_6d* state. No test is performed if :pycode:`ring` is not a :py:class:`.Lattice`. Parameters: is_6d: Desired 6D state Raises: AtError: if :pycode:`ring.is_6d` is not *is_6d* Example: >>> @check_6d(False) ... def find_orbit4(ring, dct=None, guess=None, **kwargs): ... Raises :py:class:`.AtError` if :pycode:`ring.is_6d` is :py:obj:`True` See Also: :py:func:`set_6d` """ def radiation_decorator(func): @functools.wraps(func) def wrapper(ring, *args, **kwargs): ringrad = getattr(ring, "is_6d", is_6d) if ringrad != is_6d: raise AtError(f'{func.__name__} needs "ring.is_6d" {is_6d}') return func(ring, *args, **kwargs) return wrapper return radiation_decorator
[docs] def set_6d(is_6d: bool) -> Callable: # noinspection PyUnresolvedReferences r"""Decorator for optics functions Wraps a function like :pycode:`func(ring, *args, **kwargs)` where :pycode:`ring` is a :py:class:`.Lattice` object. If :pycode:`ring.is_6d` is not the desired *is_6d* state, :pycode:`func()` will be called with a modified copy of :pycode:`ring` satisfying the *is_6d* state. Parameters: is_6d: Desired 6D state Example: >>> @set_6d(True) ... def compute(ring) ... is roughly equivalent to: >>> if ring.is_6d: ... compute(ring) ... else: ... compute(ring.enable_6d(copy=True)) See Also: :py:func:`check_6d`, :py:meth:`.Lattice.enable_6d`, :py:meth:`.Lattice.disable_6d` """ if is_6d: def setrad_decorator(func): @functools.wraps(func) def wrapper(ring, *args, **kwargs): rg = ring if ring.is_6d else ring.enable_6d(copy=True) return func(rg, *args, **kwargs) return wrapper else: def setrad_decorator(func): @functools.wraps(func) def wrapper(ring, *args, **kwargs): rg = ring.disable_6d(copy=True) if ring.is_6d else ring return func(rg, *args, **kwargs) return wrapper return setrad_decorator
[docs] def make_copy(copy: bool) -> Callable: r"""Decorator for optics functions Wraps a function like :pycode:`func(ring, refpts, *args, **kwargs)` where :pycode:`ring` is a :py:class:`.Lattice` object. If *copy* is :py:obj:`False`, the function is not modified, If *copy* is :py:obj:`True`, a shallow copy of :pycode:`ring` is done, then the elements selected by *refpts* are deep-copied, then :pycode:`func` is applied to the copy, and the new :pycode:`ring` is returned. Parameters: copy: If :py:obj:`True`, apply the decorated function to a copy of :pycode:`ring` """ if copy: def copy_decorator(func): @functools.wraps(func) def wrapper(ring, refpts, *args, **kwargs): try: ring = ring.replace(refpts) except AttributeError: check = get_bool_index(ring, refpts) ring = [el.deepcopy() if ok else el for el, ok in zip(ring, check)] func(ring, refpts, *args, **kwargs) return ring return wrapper else: def copy_decorator(func): return func return copy_decorator
[docs] def uint32_refpts( refpts: RefIndex, n_elements: int, endpoint: bool = True, types: str = _typ1 ) -> Uint32Refpts: r"""Return a :py:obj:`~numpy.uint32` array of element indices selecting ring elements. Parameters: refpts: Element selector. *refpts* may be: #. an integer or a sequence of integers (0 indicating the first element), #. a sequence of booleans marking the selected elements, #. :py:obj:`None`, meaning empty selection, #. :py:obj:`.All`, meaning "all possible reference points", #. :py:obj:`.End`, selecting the end of the last element. n_elements: Length of the sequence of elements endpoint: if :py:obj:`True`, allow *n_elements* as a special index, referring to the end of the last element. types: Allowed types Returns: uint32_ref (Uint32Refpts): :py:obj:`~numpy.uint32` numpy array used for indexing :py:class:`.Element`\ s in a lattice. """ refs = numpy.ravel(refpts) if refpts is RefptsCode.All: stop = n_elements + 1 if endpoint else n_elements return numpy.arange(stop, dtype=numpy.uint32) elif refpts is RefptsCode.End: if not endpoint: raise IndexError('"End" index out of range') return numpy.array([n_elements], dtype=numpy.uint32) elif (refpts is None) or (refs.size == 0): return numpy.array([], dtype=numpy.uint32) elif numpy.issubdtype(refs.dtype, numpy.bool_): return numpy.flatnonzero(refs).astype(numpy.uint32) elif numpy.issubdtype(refs.dtype, numpy.integer): # Handle negative indices if endpoint: refs = numpy.array( [i if (i == n_elements) else i % n_elements for i in refs], dtype=numpy.uint32, ) else: refs = numpy.array([i % n_elements for i in refs], dtype=numpy.uint32) # Check ascending if refs.size > 1: prev = refs[0] for nxt in refs[1:]: if nxt < prev: raise IndexError("Index out of range or not in ascending order") elif nxt == prev: raise IndexError("Duplicated index") prev = nxt return refs else: raise _type_error(refpts, types)
# noinspection PyIncorrectDocstring
[docs] def get_uint32_index( ring: Sequence[Element], refpts: Refpts, endpoint: bool = True, regex: bool = False ) -> Uint32Refpts: # noinspection PyUnresolvedReferences, PyShadowingNames r"""Returns an integer array of element indices, selecting ring elements. Parameters: refpts: Element selection key. See ":ref:`Selecting elements in a lattice <refpts>`" endpoint: if :py:obj:`True`, allow *len(ring)* as a special index, referring to the end of the last element. regex: Use regular expression for *refpts* string matching instead of Unix shell-style wildcards. Returns: uint32_ref (Uint32Refpts): uint32 numpy array used for indexing :py:class:`.Element`\ s in a lattice. Examples: >>> get_uint32_index(ring, at.Sextupole) array([ 21, 27, 35, 89, 97, 103], dtype=uint32) numpy array of indices of all :py:class:`.Sextupole`\ s >>> get_uint32_index(ring, at.End) array([121], dtype=uint32) numpy array([:pycode:`len(ring)+1`]) >>> get_uint32_index(ring, at.checkattr("Frequency")) array([0], dtype=uint32) numpy array of indices of all elements having a 'Frequency' attribute """ if isinstance(refpts, type): checkfun = checktype(refpts) elif callable(refpts): checkfun = refpts elif isinstance(refpts, Element): checkfun = checktype(type(refpts)) elif isinstance(refpts, str): checkfun = checkname(refpts, regex=regex) else: return uint32_refpts(refpts, len(ring), endpoint=endpoint, types=_typ2) return numpy.fromiter( (i for i, el in enumerate(ring) if checkfun(el)), dtype=numpy.uint32 )
[docs] def bool_refpts( refpts: RefIndex, n_elements: int, endpoint: bool = True, types: str = _typ1 ) -> BoolRefpts: r"""Returns a :py:class:`bool` array of element indices, selecting ring elements. Parameters: refpts: Element selector. *refpts* may be: #. an integer or a sequence of integers (0 indicating the first element), #. a sequence of booleans marking the selected elements, #. :py:obj:`None`, meaning empty selection, #. :py:obj:`.All`, meaning "all possible reference points", #. :py:obj:`.End`, selecting the end of the last element. n_elements: Length of the lattice endpoint: if :py:obj:`True`, allow *n_elements* as a special index, referring to the end of the last element. types: Allowed types Returns: bool_refs (BoolRefpts): A bool numpy array used for indexing :py:class:`.Element`\ s in a lattice. """ refs = numpy.ravel(refpts) stop = n_elements + 1 if endpoint else n_elements if refpts is RefptsCode.All: return numpy.ones(stop, dtype=bool) elif refpts is RefptsCode.End: if not endpoint: raise IndexError('"End" index out of range') brefpts = numpy.zeros(stop, dtype=bool) brefpts[n_elements] = True return brefpts elif (refpts is None) or (refs.size == 0): return numpy.zeros(stop, dtype=bool) elif numpy.issubdtype(refs.dtype, numpy.bool_): diff = stop - refs.size if diff <= 0: return refs[:stop] else: return numpy.append(refs, numpy.zeros(diff, dtype=bool)) elif numpy.issubdtype(refs.dtype, numpy.integer): brefpts = numpy.zeros(stop, dtype=bool) brefpts[refs] = True return brefpts else: raise _type_error(refpts, types)
# noinspection PyIncorrectDocstring
[docs] def get_bool_index( ring: Sequence[Element], refpts: Refpts, endpoint: bool = True, regex: bool = False ) -> BoolRefpts: # noinspection PyUnresolvedReferences, PyShadowingNames r"""Returns a bool array of element indices, selecting ring elements. Parameters: refpts: Element selection key. See ":ref:`Selecting elements in a lattice <refpts>`" endpoint: if :py:obj:`True`, allow *len(ring)* as a special index, referring to the end of the last element. regex: Use regular expression for *refpts* string matching instead of Unix shell-style wildcards. Returns: bool_refs (BoolRefpts): A bool numpy array used for indexing :py:class:`.Element`\ s in a lattice. Examples: >>> refpts = get_bool_index(ring, at.Quadrupole) Returns a numpy array of booleans where all :py:class:`.Quadrupole` are :py:obj:`True` >>> refpts = get_bool_index(ring, "Q[FD]*") Returns a numpy array of booleans where all elements whose *FamName* matches "Q[FD]*" are :py:obj:`True` >>> refpts = get_bool_index(ring, at.checkattr("K", 0.0)) Returns a numpy array of booleans where all elements whose *K* attribute is 0.0 are :py:obj:`True` >>> refpts = get_bool_index(ring, None) Returns a numpy array of *len(ring)+1* :py:obj:`False` values """ if isinstance(refpts, type): checkfun = checktype(refpts) elif callable(refpts): checkfun = refpts elif isinstance(refpts, Element): checkfun = checktype(type(refpts)) elif isinstance(refpts, str): checkfun = checkname(refpts, regex=regex) else: return bool_refpts(refpts, len(ring), endpoint=endpoint, types=_typ2) boolrefs = numpy.fromiter(map(checkfun, ring), dtype=bool, count=len(ring)) if endpoint: boolrefs = numpy.append(boolrefs, False) return boolrefs
[docs] def checkattr(attrname: str, attrvalue=None) -> ElementFilter: # noinspection PyUnresolvedReferences r"""Checks the presence or the value of an attribute Returns a function to be used as an :py:class:`.Element` filter, which checks the presence or the value of an attribute of the provided :py:class:`.Element`. This function can be used to extract from a ring all elements having a given attribute. Parameters: attrname: Attribute name attrvalue: Attribute value. If absent, the returned function checks the presence of an *attrname* attribute. If present, the returned function checks if :pycode:`attrname == attrvalue`. Returns: checkfun (ElementFilter): Element filter function Examples: >>> cavs = filter(checkattr("Frequency"), ring) Returns an iterator over all elements in *ring* that have a :pycode:`Frequency` attribute >>> elts = filter(checkattr("K", 0.0), ring) Returns an iterator over all elements of ring that have a :pycode:`K` attribute equal to 0.0 """ if attrvalue is None: return functools.partial(_chkattr, attrname) else: return functools.partial(_chkattrval, attrname, attrvalue)
[docs] def checktype(eltype: type | tuple[type, ...]) -> ElementFilter: # noinspection PyUnresolvedReferences r"""Checks the type of an element Returns a function to be used as an :py:class:`.Element` filter, which checks the type of the provided :py:class:`.Element`. This function can be used to extract from a ring all elements having a given type. Parameters: eltype: Desired :py:class:`.Element` type Returns: checkfun (ElementFilter): Element filter function Examples: >>> qps = filter(checktype(at.Quadrupole), ring) Returns an iterator over all quadrupoles in ring """ return functools.partial(_chktype, eltype)
[docs] def checkname(pattern: str, regex: bool = False) -> ElementFilter: # noinspection PyUnresolvedReferences r"""Checks the name of an element Returns a function to be used as an :py:class:`.Element` filter, which checks the name of the provided :py:class:`.Element`. This function can be used to extract from a ring all elements having a given name. Parameters: pattern: Desired :py:class:`.Element` name. Unix shell-style wildcards are supported (see :py:func:`fnmatch.fnmatch`) regex: Use regular expression for *refpts* string matching instead of Unix shell-style wildcards. Returns: checkfun (ElementFilter): Element filter function Examples: >>> qps = filter(checkname("QF*"), ring) Returns an iterator over all the elements with name starting with ``QF``. """ if regex: return functools.partial(_chkregex, pattern) else: return functools.partial(_chkpattern, pattern)
[docs] def refpts_iterator( ring: Sequence[Element], refpts: Refpts, regex: bool = False ) -> Iterator[Element]: r"""Return an iterator over selected elements in a lattice Parameters: ring: Lattice description refpts: Element selection key. See ":ref:`Selecting elements in a lattice <refpts>`" regex: Use regular expression for *refpts* string matching instead of Unix shell-style wildcards. Returns: elem_iter (Iterator[Element]): Iterator over the elements in *ring* selected by *refpts*. """ if isinstance(refpts, type): checkfun = checktype(refpts) elif callable(refpts): checkfun = refpts elif isinstance(refpts, Element): checkfun = checktype(type(refpts)) elif isinstance(refpts, str): checkfun = checkname(refpts, regex=regex) else: refs = numpy.ravel(refpts) if refpts is RefptsCode.All: return (el for el in ring) elif refpts is RefptsCode.End: raise IndexError('"End" is not allowed for endpoint=False') elif (refpts is None) or (refs.size == 0): return iter(()) elif numpy.issubdtype(refs.dtype, numpy.bool_): return compress(ring, refs) elif numpy.issubdtype(refs.dtype, numpy.integer): return (ring[i] for i in refs) else: raise _type_error(refpts, _typ2) return filter(checkfun, ring)
# noinspection PyUnusedLocal,PyIncorrectDocstring
[docs] def refpts_count( refpts: RefIndex, n_elements: int, endpoint: bool = True, types: str = _typ1 ) -> int: r"""Returns the number of reference points Parameters: refpts: refpts may be: #. an integer or a sequence of integers (0 indicating the first element), #. a sequence of booleans marking the selected elements, #. :py:obj:`None`, meaning empty selection, #. :py:obj:`.All`, meaning "all possible reference points", #. :py:obj:`.End`, selecting the end of the last element. n_elements: Lattice length endpoint: if :py:obj:`True`, allow *n_elements* as a special index, referring to the end of the last element. Returns: nrefs (int): The number of reference points """ refs = numpy.ravel(refpts) if refpts is RefptsCode.All: return n_elements + 1 if endpoint else n_elements elif refpts is RefptsCode.End: if not endpoint: raise IndexError('"End" index out of range') return 1 elif (refpts is None) or (refs.size == 0): return 0 elif numpy.issubdtype(refs.dtype, numpy.bool_): return numpy.count_nonzero(refs) elif numpy.issubdtype(refs.dtype, numpy.integer): return len(refs) else: raise _type_error(refpts, types)
def _refcount( ring: Sequence[Element], refpts: Refpts, endpoint: bool = True, regex: bool = False ) -> int: # noinspection PyUnresolvedReferences, PyShadowingNames r"""Returns the number of reference points Parameters: refpts: Element selection key. See ":ref:`Selecting elements in a lattice <refpts>`" endpoint: if :py:obj:`True`, allow *len(ring)* as a special index, referring to the end of the last element. regex: Use regular expression for *refpts* string matching instead of Unix shell-style wildcards. Returns: nrefs (int): The number of reference points Examples: >>> refpts = ring.refcount(at.Sextupole) 6 Returns the number of :py:class:`.Sextupole`\ s in the lattice >>> refpts = ring.refcount(at.All) 122 Returns *len(ring)+1* >>> refpts = ring.refcount(at.All, endpoint=False) 121 Returns *len(ring)* """ if isinstance(refpts, type): checkfun = checktype(refpts) elif callable(refpts): checkfun = refpts elif isinstance(refpts, Element): checkfun = checktype(type(refpts)) elif isinstance(refpts, str): checkfun = checkname(refpts, regex=regex) else: return refpts_count(refpts, len(ring), endpoint=endpoint, types=_typ2) return len(list(filter(checkfun, ring))) # noinspection PyUnusedLocal,PyIncorrectDocstring
[docs] def get_elements(ring: Sequence[Element], refpts: Refpts, regex: bool = False) -> list: r"""Returns a list of elements selected by *key*. Deprecated: :pycode:`get_elements(ring, refpts)` is :pycode:`ring[refpts]` Parameters: ring: Lattice description refpts: Element selection key. See ":ref:`Selecting elements in a lattice <refpts>`" regex: Use regular expression for *refpts* string matching instead of Unix shell-style wildcards. Returns: elem_list (list): list of :py:class:`.Element`\ s matching key """ return list(refpts_iterator(ring, refpts, regex=regex))
[docs] def get_value_refpts( ring: Sequence[Element], refpts: Refpts, attrname: str, index: int | None = None, regex: bool = False, ): r"""Extracts attribute values from selected lattice :py:class:`.Element`\ s. Parameters: ring: Lattice description refpts: Element selection key. See ":ref:`Selecting elements in a lattice <refpts>`" attrname: Attribute name index: index of the value to retrieve if *attrname* is an array. If :py:obj:`None` the full array is retrieved In case *attrname* is a scalar attribute, the keyword argument index has to be None regex: Use regular expression for *refpts* string matching instead of Unix shell-style wildcards. Returns: attrvalues: numpy Array of attribute values. """ getf = getval(attrname, index=index) return numpy.array( [getf(elem) for elem in refpts_iterator(ring, refpts, regex=regex)] )
[docs] def set_value_refpts( ring: Sequence[Element], refpts: Refpts, attrname: str, attrvalues, index: int | None = None, increment: bool = False, copy: bool = False, regex: bool = False, ): r"""Set the values of an attribute of an array of elements based on their refpts Parameters: ring: Lattice description refpts: Element selection key. See ":ref:`Selecting elements in a lattice <refpts>`" attrname: Attribute name attrvalues: Attribute values index: index of the value to set if *attrname* is an array. if :py:obj:`None`, the full array is replaced by *attrvalue*. In case *attrname* is a scalar attribute, the keyword argument index has to be None increment: If :py:obj:`True`, *attrvalues* are added to the initial values. regex: Use regular expression for *refpts* string matching instead of Unix shell-style wildcards. copy: If :py:obj:`False`, the modification is done in-place, If :py:obj:`True`, return a shallow copy of the lattice. Only the modified elements are copied. .. Caution:: a shallow copy means that all non-modified elements are shared with the original lattice. Any further modification will affect both lattices. """ setf = setval(attrname, index=index) if increment: attrvalues += get_value_refpts(ring, refpts, attrname, index=index, regex=regex) else: attrvalues = numpy.broadcast_to( attrvalues, (_refcount(ring, refpts, regex=regex),) ) # noinspection PyShadowingNames @make_copy(copy) def apply(ring, refpts, values, regex): for elm, val in zip(refpts_iterator(ring, refpts, regex=regex), values): setf(elm, val) return apply(ring, refpts, attrvalues, regex)
[docs] def get_s_pos( ring: Sequence[Element], refpts: Refpts = All, regex: bool = False ) -> Sequence[float]: # noinspection PyUnresolvedReferences r"""Returns the locations of selected elements Parameters: ring: Lattice description refpts: Element selection key. See ":ref:`Selecting elements in a lattice <refpts>`" regex: Use regular expression for *refpts* string matching instead of Unix shell-style wildcards. Returns: s_pos: Array of locations of the elements selected by *refpts* Example: >>> get_s_pos(ring, at.End) array([26.37428795]) Position at the end of the last element: length of the lattice """ # Positions at the end of each element. s_pos = numpy.cumsum([getattr(el, "Length", 0.0) for el in ring]) # Prepend position at the start of the first element. s_pos = numpy.concatenate(([0.0], s_pos)) return s_pos[get_bool_index(ring, refpts, regex=regex)]