"""Definition of variables.
Variables are **references** to scalar attributes of lattice elements. There are 2
kinds of element variables:
- an :py:class:`ElementVariable` is associated to an element object, and acts on all
occurences of this object. But it will not affect any copy, neither shallow nor deep,
of the original object,
- a :py:class:`RefptsVariable` is not associated to an element object, but to an element
location in a :py:class:`.Lattice`. It acts on any copy of the initial lattice. A
*ring* argument must be provided to the *set* and *get* methods to identify the
lattice, which may be a possibly modified copy of the original lattice
"""
from __future__ import annotations
__all__ = ["ElementVariable", "RefptsVariable"]
from collections.abc import Sequence, Callable
from typing import Any
import numpy as np
from .elements import Element
from .lattice_object import Lattice
from .utils import Refpts, getval, setval
from .variables import VariableBase, Number
[docs]
class RefptsVariable(VariableBase):
r"""A reference to a scalar attribute of :py:class:`.Lattice` elements.
It can refer to:
* a scalar attribute or
* an element of an array attribute
of one or several :py:class:`.Element`\ s of a lattice.
A :py:class:`RefptsVariable` is not associated to element objets themselves, but
to the location of these elements in a lattice. So a :py:class:`RefptsVariable`
will act equally on any copy of the initial ring.
As a consequence, a *ring* keyword argument (:py:class:`.Lattice` object) must be
supplied for getting or setting the variable.
"""
_getf: Callable[[Element], Number]
_setf: Callable[[Element, Number], None]
refpts: Refpts
def __init__(
self, refpts: Refpts, attrname: str, index: int | None = None, **kwargs
):
r"""
Parameters:
refpts: Location of variable :py:class:`.Element`\ s
attrname: Attribute name
index: Index in the attribute array. Use :py:obj:`None` for
scalar attributes.
Keyword Args:
name (str): Name of the Variable. Default: ``''``
bounds (tuple[float, float]): Lower and upper bounds of the
variable value. Default: (-inf, inf)
delta (float): Step. Default: 1.0
ring (Lattice): Default lattice. It will be used if *ring* is not provided
to the :py:meth:`~.VariableBase.get` or :py:meth:`~.VariableBase.set`
methods. Note that if *ring* is fixed, you should consider using a
:py:class:`.ElementVariable` instead.
"""
self._getf = getval(attrname, index=index)
self._setf = setval(attrname, index=index)
self.refpts = refpts
super().__init__(**kwargs)
def _setfun(self, value: Number, ring: Lattice | None = None, **_) -> None:
if ring is None:
msg = (
"Can't set values if ring is None.\n"
"Try to use an ElementVariable if possible"
)
raise ValueError(msg)
for elem in ring.select(self.refpts):
self._setf(elem, value)
def _getfun(self, ring: Lattice | None = None, **_) -> Number:
if ring is None:
msg = "Can't get values if ring is None"
raise ValueError(msg)
values = np.array([self._getf(elem) for elem in ring.select(self.refpts)])
return np.average(values)
[docs]
class ElementVariable(VariableBase):
r"""A reference to a scalar attribute of :py:class:`.Lattice` elements.
It can refer to:
* a scalar attribute or
* an element of an array attribute
of one or several :py:class:`.Element`\ s of a lattice.
An :py:class:`ElementVariable` is associated to an element object, and acts on all
occurrences of this object. But it will not affect any copy, neither shallow nor
deep, of the original object.
"""
_getf: Callable[[Element], Any]
_setf: Callable[[Element, Any], None]
_elements: set[Element]
def __init__(
self,
elements: Element | Sequence[Element],
attrname: str,
index: int | None = None,
**kwargs,
):
r"""
Parameters:
elements: :py:class:`.Element` or Sequence[Element] whose
attribute is varied
attrname: Attribute name
index: Index in the attribute array. Use :py:obj:`None` for
scalar attributes.
Keyword Args:
name (str): Name of the Variable. Default: ``''``
bounds (tuple[float, float]): Lower and upper bounds of the
variable value. Default: (-inf, inf)
delta (float): Step. Default: 1.0
"""
# Ensure the uniqueness of elements
if isinstance(elements, Element):
self._elements = {elements}
else:
self._elements = set(elements)
self._getf = getval(attrname, index=index)
self._setf = setval(attrname, index=index)
super().__init__(**kwargs)
def _setfun(self, value: Number, **_) -> None:
for elem in self._elements:
self._setf(elem, value)
def _getfun(self, **_) -> Number:
values = np.array([self._getf(elem) for elem in self._elements])
return np.average(values)
@property
def elements(self) -> set[Element]:
"""Return the set of elements acted upon by the variable."""
return self._elements