From 3dde4eb85b0843dc7187fed2d02e0b914014d5d3 Mon Sep 17 00:00:00 2001 From: Alexis Gamelin Date: Thu, 17 Sep 2026 10:22:53 +0200 Subject: [PATCH 01/49] Honour lattice_names when a linker is configured (#429) With a linker on the simulator, Simulator.get_at_elems resolved a PyAML element only through the linker identifier built from the element name and ignored the lattice_names selector, so one PyAT element could not be driven by several PyAML elements (e.g. a serialized sextupole and the corrector coils sitting on the same thick element). - LatticeElementsLinker.get_identifier_for_name(name): identifier for a name of a lattice_names selector (implemented by the attribute linker) - Simulator.get_at_elems: with a linker, support list(a,b), [name]@i,j and [name]#a..b (bare @/# index the whole ring) through the linker - document that name refers to the linker attribute when a linker is used - tests: simulator-level selector tests on a Tag-linked lattice and the EBSNames addressing test parametrized with an equivalent linker config Fixes #429 Co-Authored-By: Claude Opus 5 --- pyaml/common/element.py | 8 +- pyaml/lattice/attribute_linker.py | 18 ++- pyaml/lattice/lattice_elements_linker.py | 27 ++++ pyaml/lattice/simulator.py | 33 ++++- tests/config/EBSNames-linker.yaml | 95 ++++++++++++++ tests/lattice/test_linkers.py | 158 +++++++++++------------ 6 files changed, 252 insertions(+), 87 deletions(-) create mode 100644 tests/config/EBSNames-linker.yaml diff --git a/pyaml/common/element.py b/pyaml/common/element.py index 7314c8d1..cdf83239 100644 --- a/pyaml/common/element.py +++ b/pyaml/common/element.py @@ -232,7 +232,9 @@ class ElementConfigModel(BaseModel): - [name]@idx[,idx] : Element indices in the subset formed by name. - [name]#start_idx..end_idx : Element range in the subset formed by name. In the above syntax, if the name is not specficied, the whole set - of lattice element is used for indexing. + of lattice element is used for indexing. ``name`` refers to the + ``FamName`` of the lattice elements, or to the attribute configured + in the simulator linker when one is used. """ model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") @@ -260,7 +262,9 @@ class Element: | ``[name]#start_idx..end_idx`` : Lattice element range in the subset formed by name. In the above syntax, if the name is not specficied, the whole set - of lattice element is used for indexing. + of lattice element is used for indexing. ``name`` refers to the + ``FamName`` of the lattice elements, or to the attribute configured + in the simulator linker when one is used. description : str or None, optional Human-readable element description. diff --git a/pyaml/lattice/attribute_linker.py b/pyaml/lattice/attribute_linker.py index affa5bcf..053ef10e 100644 --- a/pyaml/lattice/attribute_linker.py +++ b/pyaml/lattice/attribute_linker.py @@ -109,7 +109,23 @@ def get_element_identifier(self, element: Element) -> LinkerIdentifier: LinkerIdentifier The identifier for linking the element """ - return PyAtAttributeIdentifier(self.linker_config_model.attribute_name, element.get_name()) + return self.get_identifier_for_name(element.get_name()) + + def get_identifier_for_name(self, name: str) -> LinkerIdentifier: + """ + Get the identifier for a name used in an element ``lattice_names`` selector. + + Parameters + ---------- + name : str + Expected value of the configured attribute. + + Returns + ------- + LinkerIdentifier + The identifier matching the lattice elements whose attribute equals ``name`` + """ + return PyAtAttributeIdentifier(self.linker_config_model.attribute_name, name) def _test_at_element(self, identifier: PyAtAttributeIdentifier, element: at.Element) -> bool: """ diff --git a/pyaml/lattice/lattice_elements_linker.py b/pyaml/lattice/lattice_elements_linker.py index ef11e559..87703667 100644 --- a/pyaml/lattice/lattice_elements_linker.py +++ b/pyaml/lattice/lattice_elements_linker.py @@ -70,6 +70,8 @@ class LatticeElementsLinker(ABC): Set the lattice for element linking. get_element_identifier(element) Get the identifier for linking an element. + get_identifier_for_name(name) + Get the identifier for a name given in an element ``lattice_names`` selector. get_at_elements(element_id) Return a list of PyAT elements matching the given identifiers. get_at_element(element_id) @@ -130,6 +132,31 @@ def get_element_identifier(self, element: Element) -> LinkerIdentifier: """ pass + def get_identifier_for_name(self, name: str) -> LinkerIdentifier: + """ + Get the identifier for a name used in an element ``lattice_names`` selector. + + Called when a PyAML element selects its lattice elements explicitly + (``lattice_names: list(a,b)``, ``a@0,1`` or ``a#0..2``): each ``name`` of the + selector is resolved through the linker instead of the element name. + + Parameters + ---------- + name : str + Name given in the ``lattice_names`` selector. + + Returns + ------- + LinkerIdentifier + The identifier matching the lattice element(s) designated by ``name``. + + Raises + ------ + NotImplementedError + If the linker does not support ``lattice_names`` selectors. + """ + raise NotImplementedError(f"{self.__class__.__name__} does not support 'lattice_names' selectors") + def _iter_matches(self, identifier: LinkerIdentifier) -> Iterable[at.Element]: """ Yield all lattice elements matching ``identifier``. diff --git a/pyaml/lattice/simulator.py b/pyaml/lattice/simulator.py index 071d66a8..10bd537f 100644 --- a/pyaml/lattice/simulator.py +++ b/pyaml/lattice/simulator.py @@ -387,6 +387,12 @@ def get_at_elems(self, element: Element) -> list[at.Element]: """ Resolve a PyAML element to matching PyAT lattice elements. + The ``lattice_names`` selector of the element is honoured with or without a + linker. With a linker, the names of the selector are resolved through + ``linker.get_identifier_for_name`` (e.g. the configured attribute for + ``pyaml.lattice.attribute_linker``) instead of ``FamName``; without a + selector the linker uses the element name. + Parameters ---------- element : Element @@ -398,11 +404,28 @@ def get_at_elems(self, element: Element) -> list[at.Element]: Matching Accelerator Toolbox lattice elements. """ if self._linker: - identifier = self._linker.get_element_identifier(element) - element_list = self._linker.get_at_elements(identifier) - if not element_list: - raise PyAMLException(f"{identifier} not found in lattice:{self._lattice}") - return element_list + if element.get_lattice_names() is None: + # By name + return self._linker.get_at_elements(self._linker.get_element_identifier(element)) + # By list + nameList = self.get_names(element) + if nameList is not None: + identifiers = [self._linker.get_identifier_for_name(name) for name in nameList] + return self._linker.get_at_elements(identifiers) + # By name or indices + name, indices = self.get_indices(element) + if name is None: + # Direct indexing in the ring + return [self.ring[idx] for idx in indices] + elts = self._linker.get_at_elements(self._linker.get_identifier_for_name(name)) + if indices is None: + return elts + try: + return [elts[idx] for idx in indices] + except IndexError as err: + strErr = f"{element.get_name()}: Invalid lattice_names index " + strErr += f"for {element.get_lattice_names()}, {len(elts)} element(s) found for {name}" + raise PyAMLException(strErr) from err else: # By list nameList = self.get_names(element) diff --git a/tests/config/EBSNames-linker.yaml b/tests/config/EBSNames-linker.yaml new file mode 100644 index 00000000..98ae50b2 --- /dev/null +++ b/tests/config/EBSNames-linker.yaml @@ -0,0 +1,95 @@ +type: pyaml.accelerator +facility: ESRF +machine: sr +energy: 6e9 +simulators: + - type: pyaml.lattice.simulator + lattice: sr/lattices/ebs-original-names.mat + name: design + linker: + type: pyaml.lattice.attribute_linker + attribute_name: FamName # equivalent to the default linker +controls: + - type: tango.pyaml.controlsystem + tango_host: ebs-simu-3:10000 + name: live + catalog: catalogs/ebs_catalogs.yaml +data_folder: /data/store +devices: +- type: pyaml.magnet.quadrupole + name: QF1E + model: + type: pyaml.magnet.linear_model + calibration_factor: 1.00054 + crosstalk: 1.0 + curve: + type: pyaml.magnet.csvcurve + file: sr/magnet_models/QF1_strength.csv + unit: 1/m + hardware_unit: A + powerconverter: srmag/vps-qf1/c04-e/current +- type: pyaml.magnet.quadrupole + name: QF1E-ALL + lattice_names: list(QF1E,QF1I) + model: + type: pyaml.magnet.linear_model + calibration_factor: 1.00054 + crosstalk: 1.0 + curve: + type: pyaml.magnet.csvcurve + file: sr/magnet_models/QF1_strength.csv + unit: 1/m + hardware_unit: A + powerconverter: srmag/vps-qf1/c04-e/current +- type: pyaml.magnet.quadrupole + name: QF1E-C05 + lattice_names: QF1E@1 + model: + type: pyaml.magnet.linear_model + calibration_factor: 0.996841 + crosstalk: 1.0 + curve: + type: pyaml.magnet.csvcurve + file: sr/magnet_models/QF1_strength.csv + unit: 1/m + hardware_unit: A + powerconverter: srmag/vps-qf1/c05-a/current +- type: pyaml.magnet.quadrupole + name: QF1E-C04-C05-C06 + lattice_names: QF1E@0,1,2 + model: + type: pyaml.magnet.linear_model + calibration_factor: 1.00191 + crosstalk: 1.0 + curve: + type: pyaml.magnet.csvcurve + file: sr/magnet_models/QF1_strength.csv + unit: 1/m + hardware_unit: A + powerconverter: srmag/vps-qf1/c05-e/current +- type: pyaml.magnet.quadrupole + name: QF1E-C04-C05-C06-2 + lattice_names: "@140,290,424" + model: + type: pyaml.magnet.linear_model + calibration_factor: 1.00191 + crosstalk: 1.0 + curve: + type: pyaml.magnet.csvcurve + file: sr/magnet_models/QF1_strength.csv + unit: 1/m + hardware_unit: A + powerconverter: srmag/vps-qf1/c05-e/current +- type: pyaml.magnet.quadrupole + name: QF1E-C04-C05-C06-3 + lattice_names: QF1E#0..3 + model: + type: pyaml.magnet.linear_model + calibration_factor: 1.00191 + crosstalk: 1.0 + curve: + type: pyaml.magnet.csvcurve + file: sr/magnet_models/QF1_strength.csv + unit: 1/m + hardware_unit: A + powerconverter: srmag/vps-qf1/c05-e/current diff --git a/tests/lattice/test_linkers.py b/tests/lattice/test_linkers.py index 75585da7..957c2cae 100644 --- a/tests/lattice/test_linkers.py +++ b/tests/lattice/test_linkers.py @@ -1,3 +1,4 @@ +import at import pytest from pyaml import PyAMLException @@ -6,6 +7,7 @@ PyAtAttributeElementsLinker, PyAtAttributeIdentifier, ) +from pyaml.lattice.simulator import Simulator # ----------------------- # Dummy PyAML Element @@ -13,14 +15,18 @@ class DummyPyAMLElement: - """Minimal stand-in for a PyAML Element: only provides .name.""" + """Minimal stand-in for a PyAML Element: only provides .name and .lattice_names.""" - def __init__(self, name: str): + def __init__(self, name: str, lattice_names: str | None = None): self._name = name + self._lattice_names = lattice_names def get_name(self) -> str: return self._name + def get_lattice_names(self) -> str | None: + return self._lattice_names + def test_conf_with_linker(): sr: Accelerator = Accelerator.load("tests/config/sr-attribute-linker.yaml") @@ -94,90 +100,27 @@ def check_index(ring, elts, indices): assert ring.index(e) == indices[idx] -def test_various_naming_addressing(): - sr = Accelerator.load("tests/config/EBSNames.yaml", ignore_external=True) +# Ring indices of the QF1E / QF1I quadrupoles of sr/lattices/ebs-original-names.mat +QF1E_INDICES = [ + 140, 290, 424, 576, 712, 848, 982, 1116, 1250, 1384, 1525, 1662, 1803, 1937, 2078, 2212, + 2348, 2482, 2616, 2762, 2898, 3046, 3182, 3316, 3452, 3593, 3732, 3873, 4009, 4150, 4286, +] # fmt: skip +QF1E_ALL_INDICES = QF1E_INDICES + [4430] + + +@pytest.mark.parametrize("config", ["tests/config/EBSNames.yaml", "tests/config/EBSNames-linker.yaml"]) +def test_various_naming_addressing(config): + """lattice_names selectors give the same elements with and without a linker.""" + sr = Accelerator.load(config, ignore_external=True) ring = sr.design.get_lattice() elts = sr.design.magnet.get("QF1E").strength._elements assert len(elts) == 31 - check_index( - ring, - elts, - [ - 140, - 290, - 424, - 576, - 712, - 848, - 982, - 1116, - 1250, - 1384, - 1525, - 1662, - 1803, - 1937, - 2078, - 2212, - 2348, - 2482, - 2616, - 2762, - 2898, - 3046, - 3182, - 3316, - 3452, - 3593, - 3732, - 3873, - 4009, - 4150, - 4286, - ], - ) + check_index(ring, elts, QF1E_INDICES) elts = sr.design.magnet.get("QF1E-ALL").strength._elements assert len(elts) == 32 - check_index( - ring, - elts, - [ - 140, - 290, - 424, - 576, - 712, - 848, - 982, - 1116, - 1250, - 1384, - 1525, - 1662, - 1803, - 1937, - 2078, - 2212, - 2348, - 2482, - 2616, - 2762, - 2898, - 3046, - 3182, - 3316, - 3452, - 3593, - 3732, - 3873, - 4009, - 4150, - 4286, - 4430, - ], - ) + check_index(ring, elts, QF1E_ALL_INDICES) elts = sr.design.magnet.get("QF1E-C05").strength._elements assert len(elts) == 1 @@ -194,3 +137,60 @@ def test_various_naming_addressing(): elts = sr.design.magnet.get("QF1E-C04-C05-C06-3").strength._elements assert len(elts) == 3 check_index(ring, elts, [140, 290, 424]) + + +# ----------------------- +# lattice_names selectors with a linker +# ----------------------- + + +@pytest.fixture +def simulator_with_tag_linker(lattice_with_custom_attr, tmp_path) -> Simulator: + """Simulator on the 'Tag' lattice: D1(Tag=D1) QF(Tag=QF) QF2(Tag=QF) QD(Tag=QD).""" + lattice_file = tmp_path / "tag_lattice.m" + at.save_m(lattice_with_custom_attr, str(lattice_file)) + return Simulator(name="design", lattice=str(lattice_file), linker=PyAtAttributeElementsLinker(attribute_name="Tag")) + + +def _tags(elements): + return [getattr(e, "Tag", None) for e in elements] + + +def test_linker_without_lattice_names_uses_element_name(simulator_with_tag_linker): + elts = simulator_with_tag_linker.get_at_elems(DummyPyAMLElement("QF")) + assert _tags(elts) == ["QF", "QF"] + + +def test_linker_lattice_names_list(simulator_with_tag_linker): + sim = simulator_with_tag_linker + assert _tags(sim.get_at_elems(DummyPyAMLElement("anything", "list(QF)"))) == ["QF", "QF"] + assert _tags(sim.get_at_elems(DummyPyAMLElement("anything", "list(QF,QD)"))) == ["QF", "QF", "QD"] + + +def test_linker_lattice_names_indices(simulator_with_tag_linker): + sim = simulator_with_tag_linker + ring = sim.ring + assert sim.get_at_elems(DummyPyAMLElement("x", "QF@1")) == [ring[2]] + assert sim.get_at_elems(DummyPyAMLElement("x", "QF@0,1")) == [ring[1], ring[2]] + assert sim.get_at_elems(DummyPyAMLElement("x", "QF#0..2")) == [ring[1], ring[2]] + # Empty name: direct indexing in the whole ring + assert sim.get_at_elems(DummyPyAMLElement("x", "@3")) == [ring[3]] + assert sim.get_at_elems(DummyPyAMLElement("x", "#1..3")) == [ring[1], ring[2]] + + +def test_linker_lattice_names_share_lattice_element(simulator_with_tag_linker): + """Two PyAML elements with different names can drive the same PyAT element.""" + sim = simulator_with_tag_linker + main = sim.get_at_elems(DummyPyAMLElement("QD")) + coil = sim.get_at_elems(DummyPyAMLElement("QD_H", "list(QD)")) + assert main == coil == [sim.ring[3]] + + +def test_linker_lattice_names_errors(simulator_with_tag_linker): + sim = simulator_with_tag_linker + with pytest.raises(PyAMLException): + sim.get_at_elems(DummyPyAMLElement("x", "list(ZZ)")) + with pytest.raises(PyAMLException): + sim.get_at_elems(DummyPyAMLElement("x", "ZZ@0")) + with pytest.raises(PyAMLException): + sim.get_at_elems(DummyPyAMLElement("x", "QF@5")) From eff076c16f5e0ec8fc38417335492719edc09817 Mon Sep 17 00:00:00 2001 From: gubaidulinvadim Date: Thu, 17 Sep 2026 15:19:30 +0200 Subject: [PATCH 02/49] Added an error throw for setting magnet with zero length. --- pyaml/lattice/abstract_impl.py | 26 +++++++++++----- tests/lattice/test_magnet_accessors.py | 41 ++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 tests/lattice/test_magnet_accessors.py diff --git a/pyaml/lattice/abstract_impl.py b/pyaml/lattice/abstract_impl.py index 91c07975..281d278c 100644 --- a/pyaml/lattice/abstract_impl.py +++ b/pyaml/lattice/abstract_impl.py @@ -12,6 +12,7 @@ from ..common import abstract from ..common.abstract_aggregator import ScalarAggregator +from ..common.exception import PyAMLException from ..magnet.model import MagnetModel from .polynom_info import PolynomInfo @@ -20,6 +21,12 @@ # ------------------------------------------------------------------------------ +def _divide_by_length(value: float, length: float) -> float: + if length == 0: + raise PyAMLException("Cannot set magnet value: lattice element length must be non-zero") + return value / length + + class RWHardwareScalar(abstract.ReadWriteFloatScalar): """ Provide read/write access to a simulated magnet in hardware units. @@ -88,7 +95,7 @@ def set(self, value: float): """ s = self._model.compute_strengths([value])[0] for idx, _ in enumerate(self._elements): - self._poly[idx][self._polyIdx] = s / (self._length * self._sign) + self._poly[idx][self._polyIdx] = _divide_by_length(s, self._length) / self._sign def set_and_wait(self, value: float): """ @@ -223,7 +230,7 @@ def set(self, value: float, polynom: str = None, polyidx: int = None): poly = [e.__getattribute__(polynom) for e in self._elements] for idx, _ in enumerate(self._elements): - poly[idx][pIdx] = value / (self._length * self._sign) + poly[idx][pIdx] = _divide_by_length(value, self._length) / self._sign # Sets the value and wait that the read value reach the setpoint def set_and_wait(self, value: float): @@ -425,19 +432,24 @@ def set(self, value: float): value : float Strength to apply to the magnet, converted to the shared hardware setpoint. """ - elements_values = [value * e.get_length() / self.get_total_length() for e in self.__elements_hardware] + elements_values = [ + _divide_by_length(value * e.get_length(), self.get_total_length()) for e in self.__elements_hardware + ] self.__element.set(elements_values[self.__element_index]) # compute the local hardware value hardware_value = self.__elements_hardware[self.__element_index].get() # compute the total hardware value - total_hardware = hardware_value * self.get_total_length() / self.get_element_length() + total_hardware = _divide_by_length( + hardware_value * self.get_total_length(), + self.get_element_length(), + ) # dispatch this value for index, element in enumerate(self.__elements_hardware): if index != self.__element_index: - element.set(total_hardware * element.get_length() / self.get_total_length()) + element.set(_divide_by_length(total_hardware * element.get_length(), self.get_total_length())) # Sets the value and wait that the read value reach the setpoint def set_and_wait(self, value: float): @@ -536,7 +548,7 @@ def set(self, value: np.array): nbStrength = len(self.__poly) s = self.__model.compute_strengths(value) for i in range(nbStrength): - self.__poly[i][self.__polyIdx[i]] = s[i] / (self.__elements[0].Length * self.__sign[i]) + self.__poly[i][self.__polyIdx[i]] = _divide_by_length(s[i], self.__elements[0].Length) / self.__sign[i] # Sets the value and wait that the read value reach the setpoint def set_and_wait(self, value: np.array): @@ -626,7 +638,7 @@ def set(self, value: np.array): nbStrength = len(self.__poly) s = np.zeros(nbStrength) for i in range(nbStrength): - self.__poly[i][self.__polyIdx[i]] = value[i] / (self.__elements[0].Length * self.__sign[i]) + self.__poly[i][self.__polyIdx[i]] = _divide_by_length(value[i], self.__elements[0].Length) / self.__sign[i] # Sets the value and wait that the read value reach the setpoint def set_and_wait(self, value: np.array): diff --git a/tests/lattice/test_magnet_accessors.py b/tests/lattice/test_magnet_accessors.py new file mode 100644 index 00000000..446d72dc --- /dev/null +++ b/tests/lattice/test_magnet_accessors.py @@ -0,0 +1,41 @@ +import at +import numpy as np +import pytest + +from pyaml import PyAMLException +from pyaml.lattice.abstract_impl import RWHardwareArray, RWHardwareScalar, RWStrengthArray, RWStrengthScalar +from pyaml.magnet.hcorrector import HCorrector +from pyaml.magnet.identity_cfm_model import IdentityCFMagnetModel +from pyaml.magnet.identity_model import IdentityMagnetModel +from pyaml.magnet.vcorrector import VCorrector + + +@pytest.mark.parametrize("accessor_type", [RWStrengthScalar, RWHardwareScalar]) +def test_setting_zero_length_scalar_magnet_raises(accessor_type): + element = at.Corrector("COR", 0.0, [0.0, 0.0], PolynomA=[0.0], PolynomB=[0.0]) + model = IdentityMagnetModel(physics="COR", unit="rad") + accessor = accessor_type([element], HCorrector.polynom, model) + + with pytest.raises(PyAMLException, match="length must be non-zero"): + accessor.set(1.0e-6) + + assert accessor.get() == 0.0 + assert np.isfinite(element.PolynomB[0]) + + +@pytest.mark.parametrize("accessor_type", [RWStrengthArray, RWHardwareArray]) +def test_setting_zero_length_combined_function_magnet_raises(accessor_type): + element = at.Corrector("COR", 0.0, [0.0, 0.0], PolynomA=[0.0], PolynomB=[0.0]) + model = IdentityCFMagnetModel( + multipoles=["B0", "A0"], + physics=["HCOR", "VCOR"], + units=["rad", "rad"], + ) + accessor = accessor_type([element], [HCorrector.polynom, VCorrector.polynom], model) + + with pytest.raises(PyAMLException, match="length must be non-zero"): + accessor.set(np.array([1.0e-6, -2.0e-6])) + + np.testing.assert_allclose(accessor.get(), np.zeros(2)) + assert np.all(np.isfinite(element.PolynomA)) + assert np.all(np.isfinite(element.PolynomB)) From b2848d265758f1b0f1ac81ae9e6b85f9f021ac01 Mon Sep 17 00:00:00 2001 From: gubaidulinvadim Date: Fri, 18 Sep 2026 09:42:05 +0200 Subject: [PATCH 03/49] Force length=1.0 if the element is thin, assuming that integrated strength is given. Issues a warning. --- pyaml/lattice/abstract_impl.py | 10 ++++++++-- tests/lattice/test_magnet_accessors.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/pyaml/lattice/abstract_impl.py b/pyaml/lattice/abstract_impl.py index 281d278c..e099d7c7 100644 --- a/pyaml/lattice/abstract_impl.py +++ b/pyaml/lattice/abstract_impl.py @@ -5,6 +5,8 @@ magnet strengths, hardware values, BPM readings, RF parameters, and tune data. """ +import warnings + import at import numpy as np from numpy.typing import NDArray @@ -12,7 +14,6 @@ from ..common import abstract from ..common.abstract_aggregator import ScalarAggregator -from ..common.exception import PyAMLException from ..magnet.model import MagnetModel from .polynom_info import PolynomInfo @@ -23,7 +24,12 @@ def _divide_by_length(value: float, length: float) -> float: if length == 0: - raise PyAMLException("Cannot set magnet value: lattice element length must be non-zero") + warnings.warn( + "Magnet length is zero; using 1.0 for strength conversion", + UserWarning, + stacklevel=2, + ) + length = 1.0 return value / length diff --git a/tests/lattice/test_magnet_accessors.py b/tests/lattice/test_magnet_accessors.py index 446d72dc..9cd63235 100644 --- a/tests/lattice/test_magnet_accessors.py +++ b/tests/lattice/test_magnet_accessors.py @@ -2,7 +2,6 @@ import numpy as np import pytest -from pyaml import PyAMLException from pyaml.lattice.abstract_impl import RWHardwareArray, RWHardwareScalar, RWStrengthArray, RWStrengthScalar from pyaml.magnet.hcorrector import HCorrector from pyaml.magnet.identity_cfm_model import IdentityCFMagnetModel @@ -11,20 +10,20 @@ @pytest.mark.parametrize("accessor_type", [RWStrengthScalar, RWHardwareScalar]) -def test_setting_zero_length_scalar_magnet_raises(accessor_type): +def test_setting_zero_length_scalar_magnet_warns_and_uses_unit_length(accessor_type): element = at.Corrector("COR", 0.0, [0.0, 0.0], PolynomA=[0.0], PolynomB=[0.0]) model = IdentityMagnetModel(physics="COR", unit="rad") accessor = accessor_type([element], HCorrector.polynom, model) - with pytest.raises(PyAMLException, match="length must be non-zero"): + with pytest.warns(UserWarning, match="Magnet length is zero; using 1.0"): accessor.set(1.0e-6) assert accessor.get() == 0.0 - assert np.isfinite(element.PolynomB[0]) + assert element.PolynomB[0] == pytest.approx(-1.0e-6) @pytest.mark.parametrize("accessor_type", [RWStrengthArray, RWHardwareArray]) -def test_setting_zero_length_combined_function_magnet_raises(accessor_type): +def test_setting_zero_length_combined_function_magnet_warns_and_uses_unit_length(accessor_type): element = at.Corrector("COR", 0.0, [0.0, 0.0], PolynomA=[0.0], PolynomB=[0.0]) model = IdentityCFMagnetModel( multipoles=["B0", "A0"], @@ -33,9 +32,10 @@ def test_setting_zero_length_combined_function_magnet_raises(accessor_type): ) accessor = accessor_type([element], [HCorrector.polynom, VCorrector.polynom], model) - with pytest.raises(PyAMLException, match="length must be non-zero"): + with pytest.warns(UserWarning, match="Magnet length is zero; using 1.0") as warning_records: accessor.set(np.array([1.0e-6, -2.0e-6])) + assert len(warning_records) == 2 np.testing.assert_allclose(accessor.get(), np.zeros(2)) - assert np.all(np.isfinite(element.PolynomA)) - assert np.all(np.isfinite(element.PolynomB)) + assert element.PolynomB[0] == pytest.approx(-1.0e-6) + assert element.PolynomA[0] == pytest.approx(-2.0e-6) From 40016c26971ce9c6657b118c287ee62657893870 Mon Sep 17 00:00:00 2001 From: gubaidulinvadim Date: Fri, 18 Sep 2026 10:05:25 +0200 Subject: [PATCH 04/49] Config example for BPM --- pyaml/bpm/bpm.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pyaml/bpm/bpm.py b/pyaml/bpm/bpm.py index dd7015c2..33bd0de8 100644 --- a/pyaml/bpm/bpm.py +++ b/pyaml/bpm/bpm.py @@ -61,6 +61,21 @@ class BPM(Element, DynamicValidation): Return the configured device key used for tilt access. get_offset_devices() Return configured device keys used for offset control. + + Examples + -------- + Example configuration: + + .. code-block:: yaml + + - type: pyaml.bpm.bpm + name: BPM_NAME + lattice_names: LATTICE_BPM_NAME + x_pos: DEVICE/X_POSITION + y_pos: DEVICE/Y_POSITION + x_offset: DEVICE/X_OFFSET + y_offset: DEVICE/Y_OFFSET + tilt: DEVICE/TILT """ def __init__( From 776acb6f0744254e4914823a2ee6b699a15b526b Mon Sep 17 00:00:00 2001 From: gubaidulinvadim Date: Fri, 18 Sep 2026 10:55:16 +0200 Subject: [PATCH 05/49] Updated to have a custom Configuration section --- docs/source/conf.py | 2 +- pyaml/bpm/bpm.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index a84769b3..687198d5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -36,7 +36,7 @@ napoleon_use_rtype = False # More legible # napoleon_numpy_docstring = False # Force consistency, leave only Google -# napoleon_custom_sections = [("Returns", "params_style")] +napoleon_custom_sections = ["Configuration"] templates_path = ["_templates"] exclude_patterns = [] diff --git a/pyaml/bpm/bpm.py b/pyaml/bpm/bpm.py index 33bd0de8..364f3899 100644 --- a/pyaml/bpm/bpm.py +++ b/pyaml/bpm/bpm.py @@ -62,10 +62,8 @@ class BPM(Element, DynamicValidation): get_offset_devices() Return configured device keys used for offset control. - Examples - -------- - Example configuration: - + Configuration + ------------- .. code-block:: yaml - type: pyaml.bpm.bpm From a39b07432f5bbc7ae30c0b98bbec36769b4accc0 Mon Sep 17 00:00:00 2001 From: gubaidulinvadim Date: Fri, 18 Sep 2026 11:01:51 +0200 Subject: [PATCH 06/49] Configuration example for diagnostics --- pyaml/diagnostics/tune_monitor.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyaml/diagnostics/tune_monitor.py b/pyaml/diagnostics/tune_monitor.py index 3b5206d7..3a771cb6 100644 --- a/pyaml/diagnostics/tune_monitor.py +++ b/pyaml/diagnostics/tune_monitor.py @@ -61,6 +61,16 @@ class BetatronTuneMonitor(Element, DynamicValidation, ABetatronTuneMonitor): Set the harmonic number used for tune-frequency conversion. attach(peer, betatron_tune) Attach the tune monitor to a peer with betatron tune data. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.diagnostics.tune_monitor + name: BETATRON_TUNE + tune_h: DEVICE/HORIZONTAL_TUNE + tune_v: DEVICE/VERTICAL_TUNE + rf_plant_name: RF """ def __init__( From 938a5822ecbd02eba8c048740720a43bd8d35fb4 Mon Sep 17 00:00:00 2001 From: gubaidulinvadim Date: Fri, 18 Sep 2026 11:02:13 +0200 Subject: [PATCH 07/49] Configuration example for RF --- pyaml/rf/rf_plant.py | 13 +++++++++++++ pyaml/rf/rf_transmitter.py | 15 +++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/pyaml/rf/rf_plant.py b/pyaml/rf/rf_plant.py index 4a005537..62e5954b 100644 --- a/pyaml/rf/rf_plant.py +++ b/pyaml/rf/rf_plant.py @@ -54,6 +54,19 @@ class RFPlant(Element, DynamicValidation): ------- attach(peer, frequency, voltage) Return a copy attached to RF read/write handles. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.rf.rf_plant + name: RF + masterclock: DEVICE/RF_FREQUENCY + transmitters: + - type: pyaml.rf.rf_transmitter + name: RF_TRANSMITTER + cavities: [CAVITY_1, CAVITY_2] + voltage: DEVICE/RF_VOLTAGE """ def __init__( diff --git a/pyaml/rf/rf_transmitter.py b/pyaml/rf/rf_transmitter.py index 69abe229..7b1677fd 100644 --- a/pyaml/rf/rf_transmitter.py +++ b/pyaml/rf/rf_transmitter.py @@ -55,6 +55,21 @@ class RFTransmitter(Element, DynamicValidation): ------- attach(peer, voltage, phase) Return a copy with voltage and phase handles attached. + + Configuration + ------------- + RF transmitters are normally nested under an + :class:`~pyaml.rf.rf_plant.RFPlant`. + + .. code-block:: yaml + + - type: pyaml.rf.rf_transmitter + name: RF_TRANSMITTER + cavities: [CAVITY_1, CAVITY_2] + voltage: DEVICE/RF_VOLTAGE + phase: DEVICE/RF_PHASE + harmonic: 1 + distribution: 1 """ def __init__( From bb7d2aa294e1b366747dd997e1be79249036b2ad Mon Sep 17 00:00:00 2001 From: gubaidulinvadim Date: Fri, 18 Sep 2026 11:02:31 +0200 Subject: [PATCH 08/49] Configuration example for tuning tools --- pyaml/tuning_tools/bba.py | 16 ++++++++++++++++ pyaml/tuning_tools/bba2.py | 16 ++++++++++++++++ pyaml/tuning_tools/chromaticity.py | 10 ++++++++++ pyaml/tuning_tools/chromaticity_monitor.py | 12 ++++++++++++ .../tuning_tools/chromaticity_response_matrix.py | 10 ++++++++++ pyaml/tuning_tools/dispersion.py | 10 ++++++++++ pyaml/tuning_tools/orbit.py | 13 +++++++++++++ pyaml/tuning_tools/orbit_response_matrix.py | 11 +++++++++++ pyaml/tuning_tools/tune.py | 10 ++++++++++ pyaml/tuning_tools/tune_response_matrix.py | 10 ++++++++++ 10 files changed, 118 insertions(+) diff --git a/pyaml/tuning_tools/bba.py b/pyaml/tuning_tools/bba.py index ed0b8cc2..10dd6cdb 100644 --- a/pyaml/tuning_tools/bba.py +++ b/pyaml/tuning_tools/bba.py @@ -106,6 +106,22 @@ class BBA(MeasurementTool, DynamicValidation): Vertical corrector used for the alignment. quadrupole : Magnet Quadrupole being aligned. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.tuning_tools.bba + name: BBA + bpm_array_name: BPM + bpm_name: BPM_NAME + hcorr_name: HORIZONTAL_CORRECTOR_NAME + vcorr_name: VERTICAL_CORRECTOR_NAME + quad_name: QUADRUPOLE_NAME + hcorr_delta: 1e-5 + vcorr_delta: 1e-5 + hquad_delta: 5e-3 + vquad_delta: 5e-3 """ def __init__( diff --git a/pyaml/tuning_tools/bba2.py b/pyaml/tuning_tools/bba2.py index b1b7bb07..25424a0c 100644 --- a/pyaml/tuning_tools/bba2.py +++ b/pyaml/tuning_tools/bba2.py @@ -183,6 +183,22 @@ class BBA2(MeasurementTool, DynamicValidation): Return the quadrupole being aligned. tune_correction Return the tune-correction tool used during alignment. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.tuning_tools.bba2 + name: BBA_WITH_TUNE_CORRECTION + bpm_array_name: BPM + bpm_name: BPM_NAME + hcorr_name: HORIZONTAL_CORRECTOR_NAME + vcorr_name: VERTICAL_CORRECTOR_NAME + quad_name: QUADRUPOLE_NAME + tune_correction_name: TUNE_CORRECTION + hcorr_delta: 1e-5 + vcorr_delta: 1e-5 + quad_delta: 5e-3 """ def __init__( diff --git a/pyaml/tuning_tools/chromaticity.py b/pyaml/tuning_tools/chromaticity.py index e1aed264..94d475ee 100644 --- a/pyaml/tuning_tools/chromaticity.py +++ b/pyaml/tuning_tools/chromaticity.py @@ -72,6 +72,16 @@ class Chromaticity(TuningTool, DynamicValidation): Calculate sextupole-strength changes for a chromaticity change. add(dchroma, wait_time=0.0) Apply a chromaticity correction relative to the current setpoint. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.tuning_tools.chromaticity + name: CHROMATICITY_CORRECTION + sextu_array_name: SEXTUPOLES + chromaticity_monitor_name: CHROMATICITY_MONITOR + response_matrix: path/to/chromaticity_response.json """ def __init__( diff --git a/pyaml/tuning_tools/chromaticity_monitor.py b/pyaml/tuning_tools/chromaticity_monitor.py index c1da2507..0f0ad6e6 100644 --- a/pyaml/tuning_tools/chromaticity_monitor.py +++ b/pyaml/tuning_tools/chromaticity_monitor.py @@ -140,6 +140,18 @@ class ChromaticityMonitor(MeasurementTool, DynamicValidation): fit(deltas, Q, order, orbit=None, fit_disp_order=None, do_plot=False) Compute chromaticity (and dispersion) from input data and update :py:attr:`~pyaml.tuning_tools.measurement_tool.MeasurementTool.latest_measurement`. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.tuning_tools.chromaticity_monitor + name: CHROMATICITY_MONITOR + betatron_tune_name: BETATRON_TUNE + rf_plant_name: RF + bpm_array_name: BPM + fit_dispersion: true + n_step: 5 """ def __init__( diff --git a/pyaml/tuning_tools/chromaticity_response_matrix.py b/pyaml/tuning_tools/chromaticity_response_matrix.py index 6746f403..c75ec53c 100644 --- a/pyaml/tuning_tools/chromaticity_response_matrix.py +++ b/pyaml/tuning_tools/chromaticity_response_matrix.py @@ -68,6 +68,16 @@ class ChromaticityResponseMatrix(MeasurementTool, DynamicValidation): Sextupole array used for the measurement. chromaticity_monitor : ChromaticityMonitor Chromaticity monitor used for the measurement. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.tuning_tools.chromaticity_response_matrix + name: CHROMATICITY_RESPONSE_MATRIX + sextu_array_name: SEXTUPOLES + chromaticity_name: CHROMATICITY_MONITOR + sextu_delta: 1e-6 """ def __init__( diff --git a/pyaml/tuning_tools/dispersion.py b/pyaml/tuning_tools/dispersion.py index 6bbc3844..e8f8f07d 100644 --- a/pyaml/tuning_tools/dispersion.py +++ b/pyaml/tuning_tools/dispersion.py @@ -65,6 +65,16 @@ class Dispersion(MeasurementTool, DynamicValidation): Measure beam dispersion by varying the RF frequency. get() Return the most recently measured dispersion data. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.tuning_tools.dispersion + name: DISPERSION + bpm_array_name: BPM + rf_plant_name: RF + frequency_delta: 100 """ def __init__(self, name: str, bpm_array_name: str, rf_plant_name: str, frequency_delta: float): diff --git a/pyaml/tuning_tools/orbit.py b/pyaml/tuning_tools/orbit.py index 3f2c8b39..feb9713c 100644 --- a/pyaml/tuning_tools/orbit.py +++ b/pyaml/tuning_tools/orbit.py @@ -94,6 +94,19 @@ class Orbit(TuningTool, DynamicValidation): Return the configured RF-frequency correction weight. post_init() Bind orbit corrector and RF handles after attachment. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.tuning_tools.orbit + name: ORBIT_CORRECTION + bpm_array_name: BPM + hcorr_array_name: HORIZONTAL_CORRECTORS + vcorr_array_name: VERTICAL_CORRECTORS + rf_plant_name: RF + singular_values: 16 + response_matrix: path/to/orbit_response.json """ def __init__( diff --git a/pyaml/tuning_tools/orbit_response_matrix.py b/pyaml/tuning_tools/orbit_response_matrix.py index 7b065b49..7d4964cc 100644 --- a/pyaml/tuning_tools/orbit_response_matrix.py +++ b/pyaml/tuning_tools/orbit_response_matrix.py @@ -98,6 +98,17 @@ class OrbitResponseMatrix(MeasurementTool, DynamicValidation): ------- measure(...) Measure orbit response matrix. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.tuning_tools.orbit_response_matrix + name: ORBIT_RESPONSE_MATRIX + bpm_array_name: BPM + hcorr_array_name: HORIZONTAL_CORRECTORS + vcorr_array_name: VERTICAL_CORRECTORS + corrector_delta: 1e-6 """ def __init__( diff --git a/pyaml/tuning_tools/tune.py b/pyaml/tuning_tools/tune.py index 068caf13..3c336d31 100644 --- a/pyaml/tuning_tools/tune.py +++ b/pyaml/tuning_tools/tune.py @@ -86,6 +86,16 @@ class Tune(TuningTool, DynamicValidation): Calculate quadrupole-strength changes for a tune change. add(dtune, wait_time=0.0) Apply a tune correction relative to the current setpoint. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.tuning_tools.tune + name: TUNE_CORRECTION + quad_array_name: QUADRUPOLES + betatron_tune_name: BETATRON_TUNE + response_matrix: path/to/tune_response.json """ def __init__( diff --git a/pyaml/tuning_tools/tune_response_matrix.py b/pyaml/tuning_tools/tune_response_matrix.py index 023b86ca..0af72d6e 100644 --- a/pyaml/tuning_tools/tune_response_matrix.py +++ b/pyaml/tuning_tools/tune_response_matrix.py @@ -109,6 +109,16 @@ class TuneResponseMatrix(MeasurementTool, DynamicValidation): Quadrupole strengths are restored after each individual scan and again when the measurement exits because of an error or interruption. + + Configuration + ------------- + .. code-block:: yaml + + - type: pyaml.tuning_tools.tune_response_matrix + name: TUNE_RESPONSE_MATRIX + quad_array_name: QUADRUPOLES + betatron_tune_name: BETATRON_TUNE + quad_delta: 1e-4 """ def __init__( From 1ef4e48d98b054a4bb16aee9bc8b815491895ecf Mon Sep 17 00:00:00 2001 From: gubaidulinvadim Date: Fri, 18 Sep 2026 11:45:55 +0200 Subject: [PATCH 09/49] Addded example config for magnets and magnet models --- pyaml/magnet/cfm_magnet.py | 15 +++++++++++++++ pyaml/magnet/hcorrector.py | 12 ++++++++++++ pyaml/magnet/identity_cfm_model.py | 12 ++++++++++++ pyaml/magnet/identity_model.py | 8 ++++++++ pyaml/magnet/linear_cfm_model.py | 17 +++++++++++++++++ pyaml/magnet/linear_model.py | 14 ++++++++++++++ pyaml/magnet/linear_serialized_model.py | 14 ++++++++++++++ pyaml/magnet/octupole.py | 12 ++++++++++++ pyaml/magnet/quadrupole.py | 12 ++++++++++++ pyaml/magnet/serialized_magnet.py | 15 +++++++++++++++ pyaml/magnet/sextupole.py | 12 ++++++++++++ pyaml/magnet/skewoctu.py | 12 ++++++++++++ pyaml/magnet/skewquad.py | 12 ++++++++++++ pyaml/magnet/skewsext.py | 12 ++++++++++++ pyaml/magnet/spline_model.py | 13 +++++++++++++ pyaml/magnet/vcorrector.py | 12 ++++++++++++ 16 files changed, 204 insertions(+) diff --git a/pyaml/magnet/cfm_magnet.py b/pyaml/magnet/cfm_magnet.py index eb7db64e..83c5578f 100644 --- a/pyaml/magnet/cfm_magnet.py +++ b/pyaml/magnet/cfm_magnet.py @@ -93,6 +93,21 @@ class CombinedFunctionMagnet(Element, DynamicValidation): PyAMLException If the mapping is invalid, if an unsupported multipole is requested, or if the model does not provide the required multipole information. + + Configuration + ------------- + The model can be defined inline or in a separate file. See + :mod:`pyaml.magnet.model` and its subclasses for model configuration. + + .. code-block:: yaml + + - type: pyaml.magnet.cfm_magnet + name: COMBINED_FUNCTION_MAGNET_NAME + mapping: + - [B2, SEXTUPOLE_NAME] + - [B0, HORIZONTAL_CORRECTOR_NAME] + - [A0, VERTICAL_CORRECTOR_NAME] + model: path/to/combined_function_magnet_model.yaml """ def __init__( diff --git a/pyaml/magnet/hcorrector.py b/pyaml/magnet/hcorrector.py index 84bfbfb2..968c4c33 100644 --- a/pyaml/magnet/hcorrector.py +++ b/pyaml/magnet/hcorrector.py @@ -44,6 +44,18 @@ class HCorrector(Magnet, DynamicValidation): ------- attach(peer, strength, hardware) Return an attached copy with a bound horizontal-angle handle. + + Configuration + ------------- + The model can be defined inline or in a separate file. See + :mod:`pyaml.magnet.model` and its subclasses for model configuration. + + .. code-block:: yaml + + - type: pyaml.magnet.hcorrector + name: HORIZONTAL_CORRECTOR_NAME + lattice_names: LATTICE_CORRECTOR_NAME + model: path/to/magnet_model.yaml """ polynom = PolynomInfo("PolynomB", 0, HORIZONTAL_KICK_SIGN) diff --git a/pyaml/magnet/identity_cfm_model.py b/pyaml/magnet/identity_cfm_model.py index 0f578f2c..497afb6e 100644 --- a/pyaml/magnet/identity_cfm_model.py +++ b/pyaml/magnet/identity_cfm_model.py @@ -71,6 +71,18 @@ class IdentityCFMagnetModel(MagnetModel, DynamicValidation): Return whether the model provides physics strengths. has_hardware() Return whether the model provides hardware values. + + Configuration + ------------- + .. code-block:: yaml + + type: pyaml.magnet.identity_cfm_model + multipoles: [B2, B0, A0] + units: [1/m**2, rad, rad] + physics: + - DEVICE/SEXTUPOLE_STRENGTH + - DEVICE/HORIZONTAL_KICK + - DEVICE/VERTICAL_KICK """ def __init__( diff --git a/pyaml/magnet/identity_model.py b/pyaml/magnet/identity_model.py index 9d5f94f6..744321d3 100644 --- a/pyaml/magnet/identity_model.py +++ b/pyaml/magnet/identity_model.py @@ -64,6 +64,14 @@ class IdentityMagnetModel(MagnetModel, DynamicValidation): ----- The model does not perform any numerical conversion: strengths are returned as hardware values and hardware values are returned as strengths. + + Configuration + ------------- + .. code-block:: yaml + + type: pyaml.magnet.identity_model + unit: 1/m + physics: DEVICE/MAGNET_STRENGTH """ def __init__( diff --git a/pyaml/magnet/linear_cfm_model.py b/pyaml/magnet/linear_cfm_model.py index 75489526..2288df68 100644 --- a/pyaml/magnet/linear_cfm_model.py +++ b/pyaml/magnet/linear_cfm_model.py @@ -101,6 +101,23 @@ class LinearCFMagnetModel(MagnetModel, DynamicValidation): Set the magnetic rigidity used for conversion. has_hardware() Return whether the model provides hardware values. + + Configuration + ------------- + .. code-block:: yaml + + type: pyaml.magnet.linear_cfm_model + multipoles: [B0, A0] + units: [rad, rad] + hardware_units: [A, A] + curves: + - type: pyaml.magnet.csvcurve + file: path/to/horizontal_excitation_curve.csv + - type: pyaml.magnet.csvcurve + file: path/to/vertical_excitation_curve.csv + powerconverters: + - DEVICE/HORIZONTAL_POWER_CONVERTER_CURRENT + - DEVICE/VERTICAL_POWER_CONVERTER_CURRENT """ def __init__( diff --git a/pyaml/magnet/linear_model.py b/pyaml/magnet/linear_model.py index 148629bf..6ffa0608 100644 --- a/pyaml/magnet/linear_model.py +++ b/pyaml/magnet/linear_model.py @@ -66,6 +66,20 @@ class LinearMagnetModel(MagnetModel, DynamicValidation): If a curve is provided, the model interpolates between strength and current values using the curve and its inverse. If no curve is provided, the model uses a simple linear relation with the stored scaling factor and offset. + + Configuration + ------------- + .. code-block:: yaml + + type: pyaml.magnet.linear_model + unit: 1/m + hardware_unit: A + calibration_factor: 1.0 + calibration_offset: 0.0 + curve: + type: pyaml.magnet.csvcurve + file: path/to/excitation_curve.csv + powerconverter: DEVICE/POWER_CONVERTER_CURRENT """ def __init__( diff --git a/pyaml/magnet/linear_serialized_model.py b/pyaml/magnet/linear_serialized_model.py index 7b30c2ea..1e912c11 100644 --- a/pyaml/magnet/linear_serialized_model.py +++ b/pyaml/magnet/linear_serialized_model.py @@ -177,6 +177,20 @@ class LinearSerializedMagnetModel(MagnetModel, DynamicValidation): ----- The number of magnets is inferred from the longest list among the supplied configuration values. Scalars are expanded to match that length. + + Configuration + ------------- + .. code-block:: yaml + + type: pyaml.magnet.linear_serialized_model + unit: 1/m + hardware_unit: A + calibration_factors: 1.0 + calibration_offsets: 0.0 + curves: + type: pyaml.magnet.csvcurve + file: path/to/excitation_curve.csv + powerconverter: DEVICE/POWER_CONVERTER_CURRENT """ def __init__( diff --git a/pyaml/magnet/octupole.py b/pyaml/magnet/octupole.py index 2247c1af..41749abf 100644 --- a/pyaml/magnet/octupole.py +++ b/pyaml/magnet/octupole.py @@ -38,6 +38,18 @@ class Octupole(Magnet, DynamicValidation): See Also -------- Magnet : Base class listing the strength and hardware accessors. + + Configuration + ------------- + The model can be defined inline or in a separate file. See + :mod:`pyaml.magnet.model` and its subclasses for model configuration. + + .. code-block:: yaml + + - type: pyaml.magnet.octupole + name: OCTUPOLE_NAME + lattice_names: LATTICE_OCTUPOLE_NAME + model: path/to/magnet_model.yaml """ polynom = PolynomInfo("PolynomB", 3) diff --git a/pyaml/magnet/quadrupole.py b/pyaml/magnet/quadrupole.py index c3b749a1..90afac55 100644 --- a/pyaml/magnet/quadrupole.py +++ b/pyaml/magnet/quadrupole.py @@ -38,6 +38,18 @@ class Quadrupole(Magnet, DynamicValidation): See Also -------- Magnet : Base class listing the strength and hardware accessors. + + Configuration + ------------- + The model can be defined inline or in a separate file. See + :mod:`pyaml.magnet.model` and its subclasses for model configuration. + + .. code-block:: yaml + + - type: pyaml.magnet.quadrupole + name: QUADRUPOLE_NAME + lattice_names: LATTICE_QUADRUPOLE_NAME + model: path/to/magnet_model.yaml """ polynom = PolynomInfo("PolynomB", 1) diff --git a/pyaml/magnet/serialized_magnet.py b/pyaml/magnet/serialized_magnet.py index 58ef4422..ddfe1733 100644 --- a/pyaml/magnet/serialized_magnet.py +++ b/pyaml/magnet/serialized_magnet.py @@ -221,6 +221,21 @@ class SerializedMagnets(Element, DynamicValidation): The serialized group stores a virtual magnet for each underlying element. When attached, each virtual magnet is bound to the same peer and the group exposes aggregate strength and hardware accessors. + + Configuration + ------------- + The model can be defined inline or in a separate file. See + :mod:`pyaml.magnet.model` and its subclasses for model configuration. + + .. code-block:: yaml + + - type: pyaml.magnet.serialized_magnet + name: SERIALIZED_QUADRUPOLES + function: B1 + elements: + - QUADRUPOLE_1 + - QUADRUPOLE_2 + model: path/to/serialized_magnet_model.yaml """ def __init__( diff --git a/pyaml/magnet/sextupole.py b/pyaml/magnet/sextupole.py index 7928c639..7380ab9e 100644 --- a/pyaml/magnet/sextupole.py +++ b/pyaml/magnet/sextupole.py @@ -38,6 +38,18 @@ class Sextupole(Magnet, DynamicValidation): See Also -------- Magnet : Base class listing the strength and hardware accessors. + + Configuration + ------------- + The model can be defined inline or in a separate file. See + :mod:`pyaml.magnet.model` and its subclasses for model configuration. + + .. code-block:: yaml + + - type: pyaml.magnet.sextupole + name: SEXTUPOLE_NAME + lattice_names: LATTICE_SEXTUPOLE_NAME + model: path/to/magnet_model.yaml """ polynom = PolynomInfo("PolynomB", 2) diff --git a/pyaml/magnet/skewoctu.py b/pyaml/magnet/skewoctu.py index b76bf3cc..7fb69abc 100644 --- a/pyaml/magnet/skewoctu.py +++ b/pyaml/magnet/skewoctu.py @@ -38,6 +38,18 @@ class SkewOctu(Magnet, DynamicValidation): See Also -------- Magnet : Base class listing the strength and hardware accessors. + + Configuration + ------------- + The model can be defined inline or in a separate file. See + :mod:`pyaml.magnet.model` and its subclasses for model configuration. + + .. code-block:: yaml + + - type: pyaml.magnet.skewoctu + name: SKEW_OCTUPOLE_NAME + lattice_names: LATTICE_SKEW_OCTUPOLE_NAME + model: path/to/magnet_model.yaml """ polynom = PolynomInfo("PolynomA", 3) diff --git a/pyaml/magnet/skewquad.py b/pyaml/magnet/skewquad.py index abf6bab9..40a7b054 100644 --- a/pyaml/magnet/skewquad.py +++ b/pyaml/magnet/skewquad.py @@ -38,6 +38,18 @@ class SkewQuad(Magnet, DynamicValidation): See Also -------- Magnet : Base class listing the strength and hardware accessors. + + Configuration + ------------- + The model can be defined inline or in a separate file. See + :mod:`pyaml.magnet.model` and its subclasses for model configuration. + + .. code-block:: yaml + + - type: pyaml.magnet.skewquad + name: SKEW_QUADRUPOLE_NAME + lattice_names: LATTICE_SKEW_QUADRUPOLE_NAME + model: path/to/magnet_model.yaml """ polynom = PolynomInfo("PolynomA", 1) diff --git a/pyaml/magnet/skewsext.py b/pyaml/magnet/skewsext.py index 5d82c1d1..cc483684 100644 --- a/pyaml/magnet/skewsext.py +++ b/pyaml/magnet/skewsext.py @@ -38,6 +38,18 @@ class SkewSext(Magnet, DynamicValidation): See Also -------- Magnet : Base class listing the strength and hardware accessors. + + Configuration + ------------- + The model can be defined inline or in a separate file. See + :mod:`pyaml.magnet.model` and its subclasses for model configuration. + + .. code-block:: yaml + + - type: pyaml.magnet.skewsext + name: SKEW_SEXTUPOLE_NAME + lattice_names: LATTICE_SKEW_SEXTUPOLE_NAME + model: path/to/magnet_model.yaml """ polynom = PolynomInfo("PolynomA", 2) diff --git a/pyaml/magnet/spline_model.py b/pyaml/magnet/spline_model.py index e3873445..cdda528c 100644 --- a/pyaml/magnet/spline_model.py +++ b/pyaml/magnet/spline_model.py @@ -70,6 +70,19 @@ class SplineMagnetModel(MagnetModel, DynamicValidation): ----- The magnet rigidity ``brho`` must be set with :meth:`set_magnet_rigidity` before using the conversion methods. + + Configuration + ------------- + .. code-block:: yaml + + type: pyaml.magnet.spline_model + unit: 1/m + hardware_unit: A + alpha: 0.0 + curve: + type: pyaml.magnet.csvcurve + file: path/to/excitation_curve.csv + powerconverter: DEVICE/POWER_CONVERTER_CURRENT """ def __init__( diff --git a/pyaml/magnet/vcorrector.py b/pyaml/magnet/vcorrector.py index f9614754..80a37990 100644 --- a/pyaml/magnet/vcorrector.py +++ b/pyaml/magnet/vcorrector.py @@ -43,6 +43,18 @@ class VCorrector(Magnet, DynamicValidation): ------- attach(peer, strength, hardware) Return an attached copy with a bound vertical-angle handle. + + Configuration + ------------- + The model can be defined inline or in a separate file. See + :mod:`pyaml.magnet.model` and its subclasses for model configuration. + + .. code-block:: yaml + + - type: pyaml.magnet.vcorrector + name: VERTICAL_CORRECTOR_NAME + lattice_names: LATTICE_CORRECTOR_NAME + model: path/to/magnet_model.yaml """ polynom = PolynomInfo("PolynomA", 0) From 7fdf3dea7b4f4210f0f6f3d965a66278085f2b0e Mon Sep 17 00:00:00 2001 From: Teresia Olsson Date: Fri, 18 Sep 2026 17:06:37 +0200 Subject: [PATCH 10/49] Update logo to version which renders correctly. --- docs/source/_static/_logo/pyaml-logo.svg | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/docs/source/_static/_logo/pyaml-logo.svg b/docs/source/_static/_logo/pyaml-logo.svg index bf5ac623..783e705a 100644 --- a/docs/source/_static/_logo/pyaml-logo.svg +++ b/docs/source/_static/_logo/pyaml-logo.svg @@ -33,8 +33,8 @@ inkscape:cy="306.36035" inkscape:window-width="1746" inkscape:window-height="1229" - inkscape:window-x="2377" - inkscape:window-y="152" + inkscape:window-x="798" + inkscape:window-y="124" inkscape:window-maximized="0" inkscape:current-layer="layer1" showguides="false" />pyAMLpyAML logo Date: Fri, 18 Sep 2026 17:21:58 +0200 Subject: [PATCH 11/49] Implement suggestion in #360 --- pyaml/lattice/abstract_impl.py | 83 +++++++++++++++----------- tests/lattice/test_magnet_accessors.py | 83 ++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 48 deletions(-) diff --git a/pyaml/lattice/abstract_impl.py b/pyaml/lattice/abstract_impl.py index e099d7c7..615d7ea0 100644 --- a/pyaml/lattice/abstract_impl.py +++ b/pyaml/lattice/abstract_impl.py @@ -5,8 +5,6 @@ magnet strengths, hardware values, BPM readings, RF parameters, and tune data. """ -import warnings - import at import numpy as np from numpy.typing import NDArray @@ -14,6 +12,7 @@ from ..common import abstract from ..common.abstract_aggregator import ScalarAggregator +from ..common.exception import PyAMLException from ..magnet.model import MagnetModel from .polynom_info import PolynomInfo @@ -22,15 +21,19 @@ # ------------------------------------------------------------------------------ -def _divide_by_length(value: float, length: float) -> float: - if length == 0: - warnings.warn( - "Magnet length is zero; using 1.0 for strength conversion", - UserWarning, - stacklevel=2, - ) - length = 1.0 - return value / length +def _effective_lengths(elements: list[at.Element]) -> list[float]: + """ + Return the lengths used to convert between integrated strengths and polynom coefficients. + + AT convention is followed: a zero-length (thin) element stores the *integrated* strength in + ``PolynomA``/``PolynomB``, so it behaves as a unit-length element for the conversion. When every element of the + group is thin, each one is given a unit length, so that the integrated strength is shared equally between the + slices. Otherwise the real lengths are used and any thin slice of the group is left untouched. + """ + lengths = [float(e.Length) for e in elements] + if all(L == 0.0 for L in lengths): + return [1.0] * len(lengths) + return lengths class RWHardwareScalar(abstract.ReadWriteFloatScalar): @@ -75,19 +78,23 @@ def __init__(self, elements: list[at.Element], poly: PolynomInfo, model: MagnetM self._poly = [e.__getattribute__(poly.attName) for e in elements] self._sign = poly.sign self._polyIdx = poly.index - self._length: float = 0.0 - for e in elements: - self._length += e.Length + self._is_thin = all(e.Length == 0 for e in elements) + self._lengths = _effective_lengths(elements) + self._length: float = sum(self._lengths) def get_length(self) -> float: - """Return the total length of the lattice elements.""" + """Return the total effective length of the lattice elements (1.0 per element when they are all thin).""" return self._length + def is_thin(self) -> bool: + """Return True when every lattice element of this magnet has zero length.""" + return self._is_thin + def get(self) -> float: """Return the current value.""" s = 0 - for idx, e in enumerate(self._elements): - s += self._poly[idx][self._polyIdx] * self._sign * e.Length + for idx, _ in enumerate(self._elements): + s += self._poly[idx][self._polyIdx] * self._sign * self._lengths[idx] return self._model.compute_hardware_values([s])[0] def set(self, value: float): @@ -101,7 +108,7 @@ def set(self, value: float): """ s = self._model.compute_strengths([value])[0] for idx, _ in enumerate(self._elements): - self._poly[idx][self._polyIdx] = _divide_by_length(s, self._length) / self._sign + self._poly[idx][self._polyIdx] = s / (self._length * self._sign) def set_and_wait(self, value: float): """ @@ -173,9 +180,8 @@ def __init__(self, elements: list[at.Element], poly: PolynomInfo, model: MagnetM self._poly = [e.__getattribute__(poly.attName) for e in elements] self._sign = poly.sign self._polyIdx = poly.index - self._length = 0 - for e in elements: - self._length += e.Length + self._lengths = _effective_lengths(elements) + self._length: float = sum(self._lengths) def get_element_length(self) -> float: """Return the total length of the represented element.""" @@ -208,8 +214,8 @@ def get(self, polynom: str = None, polyidx: int = None) -> float: poly = [e.__getattribute__(polynom) for e in self._elements] s = 0 - for idx, e in enumerate(self._elements): - s += poly[idx][pIdx] * self._sign * e.Length + for idx, _ in enumerate(self._elements): + s += poly[idx][pIdx] * self._sign * self._lengths[idx] return s # Sets the value @@ -236,7 +242,7 @@ def set(self, value: float, polynom: str = None, polyidx: int = None): poly = [e.__getattribute__(polynom) for e in self._elements] for idx, _ in enumerate(self._elements): - poly[idx][pIdx] = _divide_by_length(value, self._length) / self._sign + poly[idx][pIdx] = value / (self._length * self._sign) # Sets the value and wait that the read value reach the setpoint def set_and_wait(self, value: float): @@ -411,6 +417,14 @@ def __init__( self.__elements_strength = elements_strength self.__elements_hardware = elements_hardware self.__element_index = element_index + # The strength is shared between the serialized magnets in proportion to their (effective) length: thick + # magnets share by length, thin magnets share equally. Mixing both has no meaningful share rule. + thin_flags = [e.is_thin() for e in elements_hardware] + if any(thin_flags) and not all(thin_flags): + raise PyAMLException( + "Serialized magnets must be either all thin (zero length) or all thick; " + f"got {sum(thin_flags)} thin out of {len(thin_flags)} magnets" + ) self.__total_length = 0 for e in self.__elements_hardware: self.__total_length += e.get_length() @@ -438,24 +452,19 @@ def set(self, value: float): value : float Strength to apply to the magnet, converted to the shared hardware setpoint. """ - elements_values = [ - _divide_by_length(value * e.get_length(), self.get_total_length()) for e in self.__elements_hardware - ] + elements_values = [value * e.get_length() / self.get_total_length() for e in self.__elements_hardware] self.__element.set(elements_values[self.__element_index]) # compute the local hardware value hardware_value = self.__elements_hardware[self.__element_index].get() # compute the total hardware value - total_hardware = _divide_by_length( - hardware_value * self.get_total_length(), - self.get_element_length(), - ) + total_hardware = hardware_value * self.get_total_length() / self.get_element_length() # dispatch this value for index, element in enumerate(self.__elements_hardware): if index != self.__element_index: - element.set(_divide_by_length(total_hardware * element.get_length(), self.get_total_length())) + element.set(total_hardware * element.get_length() / self.get_total_length()) # Sets the value and wait that the read value reach the setpoint def set_and_wait(self, value: float): @@ -527,6 +536,7 @@ def __init__(self, elements: list[at.Element], poly: list[PolynomInfo], model: M self.__polyIdx = [] self.__sign = [] self.__model = model + self.__length = _effective_lengths(elements[:1])[0] for p in poly: self.__poly.append(elements[0].__getattribute__(p.attName)) self.__polyIdx.append(p.index) @@ -538,7 +548,7 @@ def get(self) -> np.array: nbStrength = len(self.__poly) s = np.zeros(nbStrength) for i in range(nbStrength): - s[i] = self.__poly[i][self.__polyIdx[i]] * self.__sign[i] * self.__elements[0].Length + s[i] = self.__poly[i][self.__polyIdx[i]] * self.__sign[i] * self.__length return self.__model.compute_hardware_values(s) # Sets the value @@ -554,7 +564,7 @@ def set(self, value: np.array): nbStrength = len(self.__poly) s = self.__model.compute_strengths(value) for i in range(nbStrength): - self.__poly[i][self.__polyIdx[i]] = _divide_by_length(s[i], self.__elements[0].Length) / self.__sign[i] + self.__poly[i][self.__polyIdx[i]] = s[i] / (self.__length * self.__sign[i]) # Sets the value and wait that the read value reach the setpoint def set_and_wait(self, value: np.array): @@ -617,6 +627,7 @@ def __init__(self, elements: list[at.Element], poly: list[PolynomInfo], model: M self.__polyIdx = [] self.__sign = [] self.__model = model + self.__length = _effective_lengths(elements[:1])[0] for p in poly: self.__poly.append(elements[0].__getattribute__(p.attName)) self.__polyIdx.append(p.index) @@ -628,7 +639,7 @@ def get(self) -> np.array: nbStrength = len(self.__poly) s = np.zeros(nbStrength) for i in range(nbStrength): - s[i] = self.__poly[i][self.__polyIdx[i]] * self.__sign[i] * self.__elements[0].Length + s[i] = self.__poly[i][self.__polyIdx[i]] * self.__sign[i] * self.__length return s # Sets the value @@ -644,7 +655,7 @@ def set(self, value: np.array): nbStrength = len(self.__poly) s = np.zeros(nbStrength) for i in range(nbStrength): - self.__poly[i][self.__polyIdx[i]] = _divide_by_length(value[i], self.__elements[0].Length) / self.__sign[i] + self.__poly[i][self.__polyIdx[i]] = value[i] / (self.__length * self.__sign[i]) # Sets the value and wait that the read value reach the setpoint def set_and_wait(self, value: np.array): diff --git a/tests/lattice/test_magnet_accessors.py b/tests/lattice/test_magnet_accessors.py index 9cd63235..e585b37e 100644 --- a/tests/lattice/test_magnet_accessors.py +++ b/tests/lattice/test_magnet_accessors.py @@ -2,29 +2,65 @@ import numpy as np import pytest -from pyaml.lattice.abstract_impl import RWHardwareArray, RWHardwareScalar, RWStrengthArray, RWStrengthScalar +from pyaml import PyAMLException +from pyaml.lattice.abstract_impl import ( + RWHardwareArray, + RWHardwareScalar, + RWSerializedStrength, + RWStrengthArray, + RWStrengthScalar, +) from pyaml.magnet.hcorrector import HCorrector from pyaml.magnet.identity_cfm_model import IdentityCFMagnetModel from pyaml.magnet.identity_model import IdentityMagnetModel from pyaml.magnet.vcorrector import VCorrector +def _thin_corrector(name="COR"): + return at.Corrector(name, 0.0, [0.0, 0.0], PolynomA=[0.0], PolynomB=[0.0]) + + @pytest.mark.parametrize("accessor_type", [RWStrengthScalar, RWHardwareScalar]) -def test_setting_zero_length_scalar_magnet_warns_and_uses_unit_length(accessor_type): - element = at.Corrector("COR", 0.0, [0.0, 0.0], PolynomA=[0.0], PolynomB=[0.0]) +def test_zero_length_scalar_magnet_stores_integrated_strength(accessor_type): + element = _thin_corrector() model = IdentityMagnetModel(physics="COR", unit="rad") accessor = accessor_type([element], HCorrector.polynom, model) - with pytest.warns(UserWarning, match="Magnet length is zero; using 1.0"): - accessor.set(1.0e-6) + accessor.set(1.0e-6) - assert accessor.get() == 0.0 + # AT convention: a thin element stores the integrated strength in its polynom assert element.PolynomB[0] == pytest.approx(-1.0e-6) + assert accessor.get() == pytest.approx(1.0e-6) + + +@pytest.mark.parametrize("accessor_type", [RWStrengthScalar, RWHardwareScalar]) +def test_zero_length_split_magnet_shares_integrated_strength_equally(accessor_type): + elements = [_thin_corrector("COR_A"), _thin_corrector("COR_B")] + model = IdentityMagnetModel(physics="COR", unit="rad") + accessor = accessor_type(elements, HCorrector.polynom, model) + + accessor.set(1.0e-6) + + assert elements[0].PolynomB[0] == pytest.approx(-0.5e-6) + assert elements[1].PolynomB[0] == pytest.approx(-0.5e-6) + assert accessor.get() == pytest.approx(1.0e-6) + + +@pytest.mark.parametrize("accessor_type", [RWStrengthScalar, RWHardwareScalar]) +def test_thick_scalar_magnet_is_unchanged(accessor_type): + element = at.Corrector("COR", 0.2, [0.0, 0.0], PolynomA=[0.0], PolynomB=[0.0]) + model = IdentityMagnetModel(physics="COR", unit="rad") + accessor = accessor_type([element], HCorrector.polynom, model) + + accessor.set(1.0e-6) + + assert element.PolynomB[0] == pytest.approx(-5.0e-6) + assert accessor.get() == pytest.approx(1.0e-6) @pytest.mark.parametrize("accessor_type", [RWStrengthArray, RWHardwareArray]) -def test_setting_zero_length_combined_function_magnet_warns_and_uses_unit_length(accessor_type): - element = at.Corrector("COR", 0.0, [0.0, 0.0], PolynomA=[0.0], PolynomB=[0.0]) +def test_zero_length_combined_function_magnet_stores_integrated_strengths(accessor_type): + element = _thin_corrector() model = IdentityCFMagnetModel( multipoles=["B0", "A0"], physics=["HCOR", "VCOR"], @@ -32,10 +68,33 @@ def test_setting_zero_length_combined_function_magnet_warns_and_uses_unit_length ) accessor = accessor_type([element], [HCorrector.polynom, VCorrector.polynom], model) - with pytest.warns(UserWarning, match="Magnet length is zero; using 1.0") as warning_records: - accessor.set(np.array([1.0e-6, -2.0e-6])) + accessor.set(np.array([1.0e-6, -2.0e-6])) - assert len(warning_records) == 2 - np.testing.assert_allclose(accessor.get(), np.zeros(2)) assert element.PolynomB[0] == pytest.approx(-1.0e-6) assert element.PolynomA[0] == pytest.approx(-2.0e-6) + np.testing.assert_allclose(accessor.get(), [1.0e-6, -2.0e-6]) + + +def test_zero_length_serialized_magnets_share_strength_equally(): + elements = [_thin_corrector("COR_A"), _thin_corrector("COR_B")] + model = IdentityMagnetModel(physics="COR", unit="rad") + strengths = [RWStrengthScalar([e], HCorrector.polynom, model) for e in elements] + currents = [RWHardwareScalar([e], HCorrector.polynom, model) for e in elements] + serialized = [RWSerializedStrength(strengths, currents, i) for i in range(len(elements))] + + serialized[0].set(1.0e-6) + + assert serialized[0].get() == pytest.approx(0.5e-6) + assert serialized[1].get() == pytest.approx(0.5e-6) + assert elements[0].PolynomB[0] == pytest.approx(-0.5e-6) + assert elements[1].PolynomB[0] == pytest.approx(-0.5e-6) + + +def test_serialized_magnets_mixing_thin_and_thick_is_rejected(): + elements = [_thin_corrector("COR_A"), at.Corrector("COR_B", 0.2, [0.0, 0.0], PolynomA=[0.0], PolynomB=[0.0])] + model = IdentityMagnetModel(physics="COR", unit="rad") + strengths = [RWStrengthScalar([e], HCorrector.polynom, model) for e in elements] + currents = [RWHardwareScalar([e], HCorrector.polynom, model) for e in elements] + + with pytest.raises(PyAMLException, match="all thin .* or all thick"): + RWSerializedStrength(strengths, currents, 0) From 741b9da5f5db936253d0c72625672499f668d121 Mon Sep 17 00:00:00 2001 From: Alexis Gamelin Date: Wed, 23 Sep 2026 13:37:39 +0200 Subject: [PATCH 12/49] Test new config for documentation --- docs/source/_static/custom.css | 4 +++ docs/source/conf.py | 54 +++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css index 29970307..ed5018f9 100644 --- a/docs/source/_static/custom.css +++ b/docs/source/_static/custom.css @@ -5,3 +5,7 @@ .api-generation { display: none; } + +.pyaml-member-summary td p { + margin-bottom: 0; +} diff --git a/docs/source/conf.py b/docs/source/conf.py index 687198d5..9fdded5a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -4,6 +4,8 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html +import re + # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information @@ -32,7 +34,8 @@ autodoc_typehints_format = "short" # autosummary_generate_overwrite = False # autosummary_ignore_module_all = False -autoclass_content = "both" # include both class docstring and __init__ +# Class docstrings document the constructor parameters; __init__ docstrings are not rendered. +autoclass_content = "class" napoleon_use_rtype = False # More legible # napoleon_numpy_docstring = False # Force consistency, leave only Google @@ -42,6 +45,55 @@ exclude_patterns = [] +# -- Class docstring "Methods"/"Attributes" sections --------------------------- +# Class docstrings list their methods and attributes so that ``help(obj)`` is +# self-contained. autodoc already documents each member from its own docstring, +# so on the rendered page the ``.. method::`` / ``.. attribute::`` blocks that +# napoleon emits for these sections are collapsed into a linked summary table +# instead of a second full description of every member. + +_MEMBER_DIRECTIVE = re.compile(r"^\.\. (method|attribute):: (\S[^(]*)") +_SUMMARY_TITLES = {"attribute": ("Attributes", "attr"), "method": ("Methods", "meth")} + + +def _read_member_block(lines, i): + """Return ``(kind, member, description, next_index)`` for the directive at ``lines[i]``.""" + kind, member = _MEMBER_DIRECTIVE.match(lines[i]).groups() + description = [] + i += 1 + while i < len(lines) and (not lines[i].strip() or lines[i].startswith(" ")): + text = lines[i].strip() + if text and not text.startswith(":"): # skip directive options such as ``:type:`` + description.append(text) + i += 1 + return kind, member.strip(), " ".join(description), i + + +def summarize_member_sections(app, what, name, obj, options, lines): + if what != "class": + return + out = [] + i = 0 + while i < len(lines): + if not _MEMBER_DIRECTIVE.match(lines[i]): + out.append(lines[i]) + i += 1 + continue + kind = _MEMBER_DIRECTIVE.match(lines[i]).group(1) + title, role = _SUMMARY_TITLES[kind] + out += [f".. rubric:: {title}", "", ".. list-table::", " :widths: 30 70", " :class: pyaml-member-summary", ""] + while i < len(lines) and (m := _MEMBER_DIRECTIVE.match(lines[i])) and m.group(1) == kind: + _, member, description, i = _read_member_block(lines, i) + out += [f" * - :py:{role}:`~{name}.{member}`", f" - {description}"] + out.append("") + lines[:] = out + + +def setup(app): + # Run after napoleon (default priority 500), which produces the member directives. + app.connect("autodoc-process-docstring", summarize_member_sections, priority=600) + + # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output From cdec557239486437bbe18b069cae0e133e97b422 Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Wed, 16 Sep 2026 17:07:04 +0200 Subject: [PATCH 13/49] Complete magnet holder navigation (#373) --- pyaml/common/holders/generic_array_holder.py | 51 ++++++++++ pyaml/common/holders/sub_holders.py | 50 ++++++++++ tests/common/test_array_holder_navigation.py | 99 ++++++++++++++++++++ tests/test_load_conf_with_code.py | 2 + 4 files changed, 202 insertions(+) create mode 100644 tests/common/test_array_holder_navigation.py diff --git a/pyaml/common/holders/generic_array_holder.py b/pyaml/common/holders/generic_array_holder.py index 0ce62441..4004a52a 100644 --- a/pyaml/common/holders/generic_array_holder.py +++ b/pyaml/common/holders/generic_array_holder.py @@ -42,6 +42,13 @@ class GenericArrayHolder(Generic[T, A]): Return a named array or a transient array of all elements. add(arrayName, elementNames) Create and register a named array from element selectors. + + Notes + ----- + A configured array is also reachable as an attribute when its name is a + valid Python identifier, e.g. ``holder.QuadForTune`` is equivalent to + ``holder.get("QuadForTune")``. Array names appear in ``dir(holder)`` so + interactive completion (IPython, Jupyter) discovers them. """ def __init__( @@ -117,5 +124,49 @@ def __getitem__(self, key): """ return self.get().__getitem__(key) + def __getattr__(self, name: str) -> A: + """ + Return a configured array through attribute access. + + Only called when normal attribute lookup fails, so it never shadows + :meth:`get`, :meth:`add`, or any other existing attribute. + + Parameters + ---------- + name : str + Configured array name. Must be a valid Python identifier. + + Returns + ------- + A + The array registered under ``name``. + + Raises + ------ + AttributeError + If ``name`` starts with an underscore, is not a valid Python + identifier, or does not match a configured array. + + Examples + -------- + >>> quad_family = sr.live.magnets.get("QuadForTune") + >>> same_quad_family = sr.live.magnets.QuadForTune + """ + if name.startswith("_") or not name.isidentifier() or name not in self._array_store: + raise AttributeError(f"'{type(self).__name__}' object has no array named '{name}'") + return self._array_store[name] + + def __dir__(self) -> list[str]: + """ + List attributes, including configured array names. + + Returns + ------- + list of str + Default attributes plus configured array names that are valid + Python identifiers, for interactive completion (IPython, Jupyter). + """ + return sorted(set(super().__dir__()) | {name for name in self._array_store if name.isidentifier()}) + def __repr__(self): return __pyaml_repr__(self) diff --git a/pyaml/common/holders/sub_holders.py b/pyaml/common/holders/sub_holders.py index 399485eb..d61088ba 100644 --- a/pyaml/common/holders/sub_holders.py +++ b/pyaml/common/holders/sub_holders.py @@ -42,6 +42,19 @@ class MagnetsHolder(GenericArrayHolder[Magnet, MagnetArray]): ---------- peer : 'ElementHolder' Parent element holder that owns this specialized holder. + + Notes + ----- + A configured magnet family is also reachable as an attribute when its + name is a valid Python identifier, e.g. ``magnets.QuadForTune`` is + equivalent to ``magnets.get("QuadForTune")``. Family names appear in + ``dir(magnets)`` for interactive completion. + + Examples + -------- + >>> quad_family = sr.live.magnets.get("QuadForTune") + >>> same_quad_family = sr.live.magnets.QuadForTune + >>> combined_function_magnets = sr.live.magnets.get_cfm() """ def __init__(self, peer: "ElementHolder"): @@ -57,6 +70,22 @@ def __init__(self, peer: "ElementHolder"): "Magnet array", ) + def get_cfm(self) -> CombinedFunctionMagnetArray: + """ + Return all combined-function magnets. + + Returns + ------- + CombinedFunctionMagnetArray + New unnamed container with every registered combined-function + magnet. + + Examples + -------- + >>> combined_function_magnets = sr.live.magnets.get_cfm() + """ + return self._peer.combined_function_magnets.get() + class CombinedFunctionMagnetHolder(GenericElementHolder[CombinedFunctionMagnet]): """ @@ -83,6 +112,13 @@ class CombinedFunctionMagnetsHolder(GenericArrayHolder[CombinedFunctionMagnet, C ---------- peer : 'ElementHolder' Parent element holder that owns this specialized holder. + + Notes + ----- + A configured array is also reachable as an attribute when its name is a + valid Python identifier, e.g. ``combined_function_magnets.CFM`` is + equivalent to ``combined_function_magnets.get("CFM")``. Array names + appear in ``dir(combined_function_magnets)`` for interactive completion. """ def __init__(self, peer: "ElementHolder"): @@ -124,6 +160,13 @@ class SerializedMagnetsHolder(GenericArrayHolder[SerializedMagnets, SerializedMa ---------- peer : 'ElementHolder' Parent element holder that owns this specialized holder. + + Notes + ----- + A configured array is also reachable as an attribute when its name is a + valid Python identifier, e.g. ``serialized_magnets.QForTune`` is + equivalent to ``serialized_magnets.get("QForTune")``. Array names appear + in ``dir(serialized_magnets)`` for interactive completion. """ def __init__(self, peer: "ElementHolder"): @@ -165,6 +208,13 @@ class BPMsHolder(GenericArrayHolder[BPM, BPMArray]): ---------- peer : 'ElementHolder' Parent element holder that owns this specialized holder. + + Notes + ----- + A configured array is also reachable as an attribute when its name is a + valid Python identifier, e.g. ``bpms.BPMS`` is equivalent to + ``bpms.get("BPMS")``. Array names appear in ``dir(bpms)`` for interactive + completion. """ def __init__(self, peer: "ElementHolder"): diff --git a/tests/common/test_array_holder_navigation.py b/tests/common/test_array_holder_navigation.py new file mode 100644 index 00000000..3fdcbe42 --- /dev/null +++ b/tests/common/test_array_holder_navigation.py @@ -0,0 +1,99 @@ +import pytest + +from pyaml.accelerator import Accelerator +from pyaml.arrays.cfm_magnet_array import CombinedFunctionMagnetArray + + +@pytest.fixture +def holder(accelerator_from_fragments, sr_configuration_fragments): + sr = accelerator_from_fragments(*sr_configuration_fragments) + sr.design.get_lattice().disable_6d() + return sr.design + + +@pytest.fixture +def serialized_holder(): + sr = Accelerator.load("tests/config/sr_serialized_magnets.yaml", include_locations=False, ignore_external=True) + return sr.design + + +def test_dynamic_attribute_returns_named_magnet_array(holder): + assert holder.magnets.HCORR is holder.magnets.get("HCORR") + assert holder.magnets.VCORR is holder.magnets.get("VCORR") + assert holder.magnets.HVCORR is holder.magnets.get("HVCORR") + + +def test_dynamic_attribute_returns_named_combined_function_magnet_array(holder): + assert holder.combined_function_magnets.CFM is holder.combined_function_magnets.get("CFM") + + +def test_dynamic_attribute_returns_named_bpm_array(holder): + assert holder.bpms.BPMS is holder.bpms.get("BPMS") + + +def test_dynamic_attribute_returns_named_serialized_magnet_array(serialized_holder): + assert serialized_holder.serialized_magnets.QForTune is serialized_holder.serialized_magnets.get("QForTune") + assert serialized_holder.serialized_magnets.series is serialized_holder.serialized_magnets.get("series") + + +def test_dynamic_attribute_unknown_name_raises_attribute_error(holder): + with pytest.raises(AttributeError): + _ = holder.magnets.UNKNOWN + + +def test_dynamic_attribute_does_not_shadow_existing_methods(holder): + holder.magnets._array_store["get"] = holder.magnets.get("HCORR") + holder.magnets._array_store["add"] = holder.magnets.get("HCORR") + + assert callable(holder.magnets.get) + assert callable(holder.magnets.add) + assert holder.magnets.get("HCORR").names() == ["SH1A-C01-H", "SH1A-C02-H"] + + +def test_dynamic_attribute_rejects_non_identifier_names(holder): + holder.magnets._array_store["not-an-id"] = holder.magnets.get("HCORR") + + with pytest.raises(AttributeError): + getattr(holder.magnets, "not-an-id") + + +def test_dir_includes_configured_array_names(holder): + assert {"HCORR", "VCORR", "HVCORR"} <= set(dir(holder.magnets)) + assert "CFM" in dir(holder.combined_function_magnets) + assert "BPMS" in dir(holder.bpms) + + +def test_dir_excludes_non_identifier_array_names(holder): + holder.magnets._array_store["not-an-id"] = holder.magnets.get("HCORR") + + assert "not-an-id" not in dir(holder.magnets) + + +def test_get_cfm_returns_all_combined_function_magnets(holder): + combined_function_magnets = holder.magnets.get_cfm() + + assert type(combined_function_magnets) is CombinedFunctionMagnetArray + assert combined_function_magnets.names() == holder.combined_function_magnets.get().names() + + +def test_issue_373_example(): + """Reproduce the #373 example verbatim, against real test lattice data.""" + sr = Accelerator.load("tests/config/EBSTune-patterns.yaml", ignore_external=True) + sr.design.get_lattice().disable_6d() + + all_magnets = sr.design.magnets[:] + quad_family = sr.design.magnets.get("QForTune") + same_quad_family = sr.design.magnets.QForTune + assert same_quad_family.names() == quad_family.names() + assert len(quad_family) == 124 + assert len(all_magnets) >= len(quad_family) + + combined_function_magnets = sr.design.magnets.get_cfm() + assert len(combined_function_magnets) == 0 + + matching_quadrupoles = sr.design.magnets["QF1*"] + assert matching_quadrupoles.names() == sr.design.magnets["QF1*"].names() + + one_magnet = sr.design.magnet.get("QF1E-C04") + one_magnet.strength.set(0.8) + assert one_magnet.strength.get() == pytest.approx(0.8) diff --git a/tests/test_load_conf_with_code.py b/tests/test_load_conf_with_code.py index de476102..e65f5be5 100644 --- a/tests/test_load_conf_with_code.py +++ b/tests/test_load_conf_with_code.py @@ -16,3 +16,5 @@ def test_load_conf_with_code(): assert sr.live["BPM*"].names() == bpms.names() assert sr.live[:].names() == [element.get_name() for element in sr.live.get_all_elements()] assert sr.design["BPM*"].names() == sr.design.bpms.get("BPM").names() + + assert sr.live.bpms.BPM is bpms From 3a8a897f8f9a82b6ac5e1a63a5421ad023dcfa0f Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Thu, 17 Sep 2026 14:58:34 +0200 Subject: [PATCH 14/49] Add a public diagnostic holder --- pyaml/common/holders/diagnostic_holder.py | 104 ++++++++++++++++++ pyaml/common/holders/element_holder.py | 9 ++ .../diagnostics/test_diagnostic_accessors.py | 60 ++++++++++ 3 files changed, 173 insertions(+) create mode 100644 pyaml/common/holders/diagnostic_holder.py create mode 100644 tests/diagnostics/test_diagnostic_accessors.py diff --git a/pyaml/common/holders/diagnostic_holder.py b/pyaml/common/holders/diagnostic_holder.py new file mode 100644 index 00000000..5fb99202 --- /dev/null +++ b/pyaml/common/holders/diagnostic_holder.py @@ -0,0 +1,104 @@ +"""Holder interface for diagnostics.""" + +from typing import TYPE_CHECKING + +from ...arrays.element_array import ElementArray +from ...diagnostics.tune_monitor import BetatronTuneMonitor +from ..element import Element, __pyaml_repr__ +from ..exception import PyAMLException + +if TYPE_CHECKING: + from .element_holder import ElementHolder + + +class DiagnosticHolder: + """ + Provide access to diagnostics. + + Parameters + ---------- + peer : 'ElementHolder' + Parent holder containing the diagnostic store. + + Attributes + ---------- + betatron_tune + Betatron tune monitor configured as ``BETATRON_TUNE``, the default, validated + against its expected class. + + Methods + ------- + get(name=None) + Return a named diagnostic, or all configured diagnostics when no name is given. + + Notes + ----- + :meth:`ElementHolder.get_betatron_tune_monitor + ` stays + available as the named, untyped lookup. This holder adds the default-name, + type-validated convenience property. + + Examples + -------- + >>> default_tune_monitor = sr.live.diagnostic.betatron_tune + >>> measured_tune = default_tune_monitor.tune.get() + >>> spare_tune_monitor = sr.live.diagnostic.get("SPARE_BETATRON_TUNE_MONITOR") + >>> all_diagnostics = sr.live.diagnostic.get() + """ + + def __init__(self, peer: "ElementHolder"): + """ + Initialize a diagnostic holder for an element holder. + """ + self._peer = peer + + def get(self, name: str = None) -> "Element | ElementArray": + """ + Return a named diagnostic, or all configured diagnostics when no name is given. + + Parameters + ---------- + name : str, optional + Name of the diagnostic to look up, as declared in the configuration. When + omitted, every configured diagnostic is returned instead. + + Returns + ------- + Element or ElementArray + The diagnostic registered under ``name``, or an + :class:`~pyaml.arrays.element_array.ElementArray` holding every configured + diagnostic, in insertion order, when ``name`` is omitted. + + Raises + ------ + PyAMLException + If ``name`` is given and no diagnostic is registered under it. + """ + if name is None: + return ElementArray("", list(self._peer._DIAG.values())) + return self._peer._get_diagnostic(name) + + @property + def betatron_tune(self) -> BetatronTuneMonitor: + """ + Return the betatron tune monitor configured as ``BETATRON_TUNE``. + + Returns + ------- + BetatronTuneMonitor + Betatron tune monitor registered under ``BETATRON_TUNE``. + + Raises + ------ + PyAMLException + If no diagnostic is registered under ``BETATRON_TUNE``, or if it is not a + :class:`~pyaml.diagnostics.tune_monitor.BetatronTuneMonitor`. + """ + name = "BETATRON_TUNE" + obj = self._peer.get_betatron_tune_monitor(name) + if not isinstance(obj, BetatronTuneMonitor): + raise PyAMLException(f"{name}: BetatronTuneMonitor expected but got {type(obj).__name__}") + return obj + + def __repr__(self): + return __pyaml_repr__(self) diff --git a/pyaml/common/holders/element_holder.py b/pyaml/common/holders/element_holder.py index aa2b16dc..cd7d5e0f 100644 --- a/pyaml/common/holders/element_holder.py +++ b/pyaml/common/holders/element_holder.py @@ -17,6 +17,7 @@ from ..abstract_aggregator import ScalarAggregator from ..element import Element from ..exception import PyAMLException +from .diagnostic_holder import DiagnosticHolder from .rf_holder import RFHolder from .sub_holders import ( BPMHolder, @@ -65,6 +66,8 @@ class ElementHolder(metaclass=ABCMeta): Single serialized magnet group by name, or a named array. rf RF plant and transmitters of this mode. + diagnostic + Diagnostics of this mode, with typed default-name access. tune, chromaticity, orbit, dispersion Tuning tools attached to this mode, looked up by name. trm, crm, orm @@ -159,6 +162,7 @@ def __init__(self): self._bpm_holder = BPMHolder(self) self._bpms_holder = BPMsHolder(self) self._rf_holder = RFHolder(self) + self._diagnostic_holder = DiagnosticHolder(self) @property def peer(self) -> "Accelerator": @@ -212,6 +216,11 @@ def rf(self) -> RFHolder: """Return the rf.""" return self._rf_holder + @property + def diagnostic(self) -> DiagnosticHolder: + """Return the diagnostic.""" + return self._diagnostic_holder + def post_init(self): """Run post-initialization hooks for every stored element.""" for e in self.get_all_elements(): diff --git a/tests/diagnostics/test_diagnostic_accessors.py b/tests/diagnostics/test_diagnostic_accessors.py new file mode 100644 index 00000000..7e878914 --- /dev/null +++ b/tests/diagnostics/test_diagnostic_accessors.py @@ -0,0 +1,60 @@ +import pytest + +from pyaml.accelerator import Accelerator +from pyaml.common.exception import PyAMLException +from pyaml.lattice.simulator import Simulator + + +def test_diagnostic_get_returns_named_monitor(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + assert design.diagnostic.get("BETATRON_TUNE") is design.get_betatron_tune_monitor("BETATRON_TUNE") + + +def test_diagnostic_get_with_no_name_returns_all_configured_diagnostics(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + all_diagnostics = design.diagnostic.get() + assert design.get_betatron_tune_monitor("BETATRON_TUNE") in all_diagnostics + + +def test_diagnostic_betatron_tune_returns_default_monitor(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + assert design.diagnostic.betatron_tune is design.get_betatron_tune_monitor("BETATRON_TUNE") + + +def test_diagnostic_raises_when_default_missing(ebs_lattice_file): + holder = Simulator(name="empty", lattice=str(ebs_lattice_file)) + + with pytest.raises(PyAMLException) as exc: + _ = holder.diagnostic.betatron_tune + assert "BETATRON_TUNE" in str(exc.value) + + +def test_diagnostic_raises_when_default_wrong_type(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + # Swap the registered default so it points to an object of the wrong type. + design._DIAG["BETATRON_TUNE"] = design.orbit + + with pytest.raises(PyAMLException) as exc: + _ = design.diagnostic.betatron_tune + assert "BETATRON_TUNE" in str(exc.value) + assert "BetatronTuneMonitor" in str(exc.value) From 5a7fea8060304a7f14eab3b40d482a6eec765147 Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Thu, 17 Sep 2026 13:40:30 +0200 Subject: [PATCH 15/49] Add a public tuning and measurement tool holder --- pyaml/common/holders/element_holder.py | 42 +++--- pyaml/common/holders/tool_holder.py | 159 ++++++++++++++++++++++ tests/tuning_tools/test_tool_accessors.py | 72 ++++++++++ 3 files changed, 257 insertions(+), 16 deletions(-) create mode 100644 pyaml/common/holders/tool_holder.py diff --git a/pyaml/common/holders/element_holder.py b/pyaml/common/holders/element_holder.py index cd7d5e0f..934dfd41 100644 --- a/pyaml/common/holders/element_holder.py +++ b/pyaml/common/holders/element_holder.py @@ -29,6 +29,7 @@ SerializedMagnetHolder, SerializedMagnetsHolder, ) +from .tool_holder import ToolHolder if TYPE_CHECKING: from ...accelerator import Accelerator @@ -68,10 +69,13 @@ class ElementHolder(metaclass=ABCMeta): RF plant and transmitters of this mode. diagnostic Diagnostics of this mode, with typed default-name access. + tool + Tuning and measurement tools of this mode, with typed default-name access. tune, chromaticity, orbit, dispersion - Tuning tools attached to this mode, looked up by name. + Backward-compatible aliases for ``tool.tune``, ``tool.chromaticity``, ``tool.orbit`` + and ``tool.dispersion``. trm, crm, orm - Response-matrix measurement tools, looked up by name. + Backward-compatible aliases for ``tool.trm``, ``tool.crm`` and ``tool.orm``. Methods ------- @@ -163,6 +167,7 @@ def __init__(self): self._bpms_holder = BPMsHolder(self) self._rf_holder = RFHolder(self) self._diagnostic_holder = DiagnosticHolder(self) + self._tool_holder = ToolHolder(self) @property def peer(self) -> "Accelerator": @@ -221,6 +226,11 @@ def diagnostic(self) -> DiagnosticHolder: """Return the diagnostic.""" return self._diagnostic_holder + @property + def tool(self) -> ToolHolder: + """Return the tool.""" + return self._tool_holder + def post_init(self): """Run post-initialization hooks for every stored element.""" for e in self.get_all_elements(): @@ -678,13 +688,13 @@ def get_crm_tuning(self, name: str) -> "ChromaticityResponseMatrix": @property def chromaticity(self) -> "Chromaticity": - """Return the chromaticity.""" - return self.get_chromaticity_tuning("DEFAULT_CHROMATICITY_CORRECTION") + """Return the chromaticity. Alias for ``tool.chromaticity``.""" + return self.tool.chromaticity @property def crm(self) -> "ChromaticityResponseMatrix": - """Return the crm.""" - return self.get_crm_tuning("DEFAULT_CHROMATICITY_RESPONSE_MATRIX") + """Return the crm. Alias for ``tool.crm``.""" + return self.tool.crm # ---- Tune --------------------------------------------------------- @@ -706,8 +716,8 @@ def get_tune_tuning(self, name: str) -> "Tune": @property def tune(self) -> "Tune": - """Return the tune.""" - return self.get_tune_tuning("DEFAULT_TUNE_CORRECTION") + """Return the tune. Alias for ``tool.tune``.""" + return self.tool.tune def get_trm_tuning(self, name: str) -> "TuneResponseMatrix": """ @@ -727,8 +737,8 @@ def get_trm_tuning(self, name: str) -> "TuneResponseMatrix": @property def trm(self) -> "TuneResponseMatrix": - """Return the default tune response-matrix tool.""" - return self.get_trm_tuning("DEFAULT_TUNE_RESPONSE_MATRIX") + """Return the default tune response-matrix tool. Alias for ``tool.trm``.""" + return self.tool.trm # ---- Orbit -------------------------------------------------------- @@ -750,8 +760,8 @@ def get_orbit_tuning(self, name: str) -> "Orbit": @property def orbit(self) -> "Orbit": - """Return the orbit.""" - return self.get_orbit_tuning("DEFAULT_ORBIT_CORRECTION") + """Return the orbit. Alias for ``tool.orbit``.""" + return self.tool.orbit def get_orm_tuning(self, name: str) -> "OrbitResponseMatrix": """ @@ -771,8 +781,8 @@ def get_orm_tuning(self, name: str) -> "OrbitResponseMatrix": @property def orm(self) -> "OrbitResponseMatrix": - """Return the default orbit response-matrix tool.""" - return self.get_orm_tuning("DEFAULT_ORBIT_RESPONSE_MATRIX") + """Return the default orbit response-matrix tool. Alias for ``tool.orm``.""" + return self.tool.orm # ---- BBA -------------------------------------------------------- @@ -812,8 +822,8 @@ def get_dispersion_tuning(self, name: str) -> "Dispersion": @property def dispersion(self) -> "Dispersion": - """Return the dispersion.""" - return self.get_dispersion_tuning("DEFAULT_DISPERSION") + """Return the dispersion. Alias for ``tool.dispersion``.""" + return self.tool.dispersion def _get_array(self, name: str): """ diff --git a/pyaml/common/holders/tool_holder.py b/pyaml/common/holders/tool_holder.py new file mode 100644 index 00000000..be7d17a0 --- /dev/null +++ b/pyaml/common/holders/tool_holder.py @@ -0,0 +1,159 @@ +"""Holder interface for tuning and measurement tools.""" + +from typing import TYPE_CHECKING + +from ...arrays.element_array import ElementArray +from ..element import Element, __pyaml_repr__ +from ..exception import PyAMLException + +if TYPE_CHECKING: + from ...tuning_tools.chromaticity import Chromaticity + from ...tuning_tools.chromaticity_response_matrix import ChromaticityResponseMatrix + from ...tuning_tools.dispersion import Dispersion + from ...tuning_tools.orbit import Orbit + from ...tuning_tools.orbit_response_matrix import OrbitResponseMatrix + from ...tuning_tools.tune import Tune + from ...tuning_tools.tune_response_matrix import TuneResponseMatrix + from .element_holder import ElementHolder + + +class ToolHolder: + """ + Provide access to tuning and measurement tools. + + Parameters + ---------- + peer : 'ElementHolder' + Parent holder containing the tool store. + + Attributes + ---------- + tune, chromaticity, orbit, dispersion + Default tuning tool for each kind, resolved from its ``DEFAULT_...`` name and + validated against the expected class. + trm, crm, orm + Default response-matrix measurement tool for each kind, resolved the same way. + + Methods + ------- + get(name=None) + Return a named tool, or all configured tools when no name is given. + + Notes + ----- + :attr:`ElementHolder.tune `, + ``.trm``, ``.orbit``, ``.orm``, ``.chromaticity``, ``.crm`` and ``.dispersion`` are + backward-compatible aliases for the corresponding properties here. + + Examples + -------- + >>> tune_correction = sr.live.tool.tune + >>> tune_correction.set([0.31, 0.22]) + >>> tune_response = sr.live.tool.trm + >>> tune_response.measure() + >>> other_tool = sr.live.tool.get("OTHER_TUNE_CORRECTION") + >>> all_tools = sr.live.tool.get() + """ + + def __init__(self, peer: "ElementHolder"): + """ + Initialize a tool holder for an element holder. + """ + self._peer = peer + + def get(self, name: str = None) -> "Element | ElementArray": + """ + Return a named tool, or all configured tools when no name is given. + + Parameters + ---------- + name : str, optional + Name of the tuning or measurement tool to look up, as declared in the + configuration. When omitted, every configured tool is returned instead. + + Returns + ------- + Element or ElementArray + The tool registered under ``name``, or an :class:`~pyaml.arrays.element_array.ElementArray` + holding every configured tool, in insertion order, when ``name`` is omitted. + + Raises + ------ + PyAMLException + If ``name`` is given and no tool is registered under it. + """ + if name is None: + return ElementArray("", list(self._peer._TOOLS.values())) + return self._peer._get_tool(name) + + def _validate_type(self, name: str, obj: Element, expected_type: type) -> Element: + """ + Ensure a resolved default tool matches the type its accessor expects. + + Raises + ------ + PyAMLException + If ``obj`` is not an instance of ``expected_type``. + """ + if not isinstance(obj, expected_type): + raise PyAMLException(f"{name}: {expected_type.__name__} expected but got {type(obj).__name__}") + return obj + + @property + def chromaticity(self) -> "Chromaticity": + """Return the chromaticity tuning tool configured as ``DEFAULT_CHROMATICITY_CORRECTION``.""" + from ...tuning_tools.chromaticity import Chromaticity + + name = "DEFAULT_CHROMATICITY_CORRECTION" + return self._validate_type(name, self._peer.get_chromaticity_tuning(name), Chromaticity) + + @property + def crm(self) -> "ChromaticityResponseMatrix": + """Return the chromaticity response-matrix tool configured as ``DEFAULT_CHROMATICITY_RESPONSE_MATRIX``.""" + from ...tuning_tools.chromaticity_response_matrix import ChromaticityResponseMatrix + + name = "DEFAULT_CHROMATICITY_RESPONSE_MATRIX" + return self._validate_type(name, self._peer.get_crm_tuning(name), ChromaticityResponseMatrix) + + @property + def tune(self) -> "Tune": + """Return the tune correction tool configured as ``DEFAULT_TUNE_CORRECTION``.""" + from ...tuning_tools.tune import Tune + + name = "DEFAULT_TUNE_CORRECTION" + return self._validate_type(name, self._peer.get_tune_tuning(name), Tune) + + @property + def trm(self) -> "TuneResponseMatrix": + """Return the tune response-matrix tool configured as ``DEFAULT_TUNE_RESPONSE_MATRIX``.""" + from ...tuning_tools.tune_response_matrix import TuneResponseMatrix + + name = "DEFAULT_TUNE_RESPONSE_MATRIX" + return self._validate_type(name, self._peer.get_trm_tuning(name), TuneResponseMatrix) + + @property + def orbit(self) -> "Orbit": + """Return the orbit correction tool configured as ``DEFAULT_ORBIT_CORRECTION``.""" + from ...tuning_tools.orbit import Orbit + + name = "DEFAULT_ORBIT_CORRECTION" + return self._validate_type(name, self._peer.get_orbit_tuning(name), Orbit) + + @property + def orm(self) -> "OrbitResponseMatrix": + """Return the orbit response-matrix tool configured as ``DEFAULT_ORBIT_RESPONSE_MATRIX``.""" + from ...tuning_tools.orbit_response_matrix import OrbitResponseMatrix + + name = "DEFAULT_ORBIT_RESPONSE_MATRIX" + return self._validate_type(name, self._peer.get_orm_tuning(name), OrbitResponseMatrix) + + @property + def dispersion(self) -> "Dispersion": + """Return the dispersion tool configured as ``DEFAULT_DISPERSION``.""" + from ...tuning_tools.dispersion import Dispersion + + name = "DEFAULT_DISPERSION" + return self._validate_type(name, self._peer.get_dispersion_tuning(name), Dispersion) + + def __repr__(self): + return __pyaml_repr__(self) diff --git a/tests/tuning_tools/test_tool_accessors.py b/tests/tuning_tools/test_tool_accessors.py index 1ccbf5df..a3dcef13 100644 --- a/tests/tuning_tools/test_tool_accessors.py +++ b/tests/tuning_tools/test_tool_accessors.py @@ -1,4 +1,8 @@ +import pytest + from pyaml.accelerator import Accelerator +from pyaml.common.exception import PyAMLException +from pyaml.lattice.simulator import Simulator def test_tuning_tools_expose_configured_elements(): @@ -51,3 +55,71 @@ def test_tuning_tools_expose_configured_elements(): assert bba.quadrupole is design.magnet.get("QF6B-C04") assert design.get_bba("BBA2-BPM_C04-04").tune_correction is design.tune + + +def test_tool_get_returns_named_tool(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + assert design.tool.get("DEFAULT_TUNE_CORRECTION") is design.tune + assert design.tool.get("DEFAULT_ORBIT_RESPONSE_MATRIX") is design.orm + + +def test_tool_get_with_no_name_returns_all_configured_tools(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + all_tools = design.tool.get() + assert design.tune in all_tools + assert design.trm in all_tools + assert design.orbit in all_tools + assert design.orm in all_tools + assert design.chromaticity in all_tools + assert design.crm in all_tools + assert design.dispersion in all_tools + + +def test_tool_typed_properties_alias_existing_defaults(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + assert design.tool.tune is design.tune + assert design.tool.trm is design.trm + assert design.tool.orbit is design.orbit + assert design.tool.orm is design.orm + assert design.tool.chromaticity is design.chromaticity + assert design.tool.crm is design.crm + assert design.tool.dispersion is design.dispersion + + +def test_tool_raises_when_default_missing(ebs_lattice_file): + holder = Simulator(name="empty", lattice=str(ebs_lattice_file)) + + with pytest.raises(PyAMLException) as exc: + _ = holder.tool.tune + assert "DEFAULT_TUNE_CORRECTION" in str(exc.value) + + +def test_tool_raises_when_default_wrong_type(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + # Swap the registered default so it points to an object of the wrong type. + design._TOOLS["DEFAULT_TUNE_CORRECTION"] = design.orbit + + with pytest.raises(PyAMLException) as exc: + _ = design.tool.tune + assert "DEFAULT_TUNE_CORRECTION" in str(exc.value) + assert "Tune" in str(exc.value) From 86928b2d08f6c7381552cd7f3f2d9be513738f3b Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Fri, 18 Sep 2026 09:41:48 +0200 Subject: [PATCH 16/49] Move BPM holders under DiagnosticHolder --- .../ControlSystem/tango/main.py | 2 +- examples/use_cases/03-orbit_correction.ipynb | 19 +++------------ examples/use_cases/03-orbit_correction.py | 2 +- pyaml/arrays/bpm.py | 2 +- pyaml/common/holders/diagnostic_holder.py | 17 +++++++++++++ pyaml/common/holders/element_holder.py | 16 ------------- pyaml/common/holders/sub_holders.py | 5 ++-- pyaml/control/controlsystem.py | 2 +- pyaml/external/pySC_interface.py | 2 +- pyaml/lattice/simulator.py | 4 +++- pyaml/tuning_tools/bba.py | 4 ++-- pyaml/tuning_tools/bba2.py | 6 ++--- pyaml/tuning_tools/chromaticity_monitor.py | 2 +- pyaml/tuning_tools/dispersion.py | 2 +- pyaml/tuning_tools/orbit.py | 2 +- pyaml/tuning_tools/orbit_response_matrix.py | 2 +- tests/arrays/test_arrays.py | 24 +++++++++---------- tests/bpm/test_bpm.py | 14 +++++------ tests/bpm/test_bpm_controlsystem.py | 10 ++++---- tests/common/test_array_holder_navigation.py | 4 ++-- .../common/test_element_holder_collection.py | 6 ++--- tests/common/test_errors.py | 2 +- .../diagnostics/test_diagnostic_accessors.py | 21 ++++++++++++++++ tests/integration/test_dt4acc_twin_smoke.py | 2 +- tests/test_accelerator_load.py | 6 ++--- tests/test_catalogs.py | 4 ++-- tests/test_load_conf_with_code.py | 6 ++--- tests/tuning_tools/test_bba.py | 2 +- tests/tuning_tools/test_tool_accessors.py | 4 ++-- tests/tuning_tools/test_tuning_dispersion.py | 2 +- .../test_tuning_orbit_correction.py | 2 +- tests/tuning_tools/test_tuning_orm.py | 2 +- 32 files changed, 106 insertions(+), 94 deletions(-) diff --git a/examples/other_examples/ControlSystem/tango/main.py b/examples/other_examples/ControlSystem/tango/main.py index 8fe0782b..4a21dc35 100644 --- a/examples/other_examples/ControlSystem/tango/main.py +++ b/examples/other_examples/ControlSystem/tango/main.py @@ -3,7 +3,7 @@ sr = Accelerator.load("config.yaml") # print the BPM position -bpm = sr.live.bpm.get("BPM_C01-01") # bpm is a BPM +bpm = sr.live.diagnostic.bpm.get("BPM_C01-01") # bpm is a BPM print(bpm.positions.get()) # Direct access to control system diff --git a/examples/use_cases/03-orbit_correction.ipynb b/examples/use_cases/03-orbit_correction.ipynb index e476b7a0..58cad3cc 100644 --- a/examples/use_cases/03-orbit_correction.ipynb +++ b/examples/use_cases/03-orbit_correction.ipynb @@ -185,24 +185,11 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "6adb7436", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "BPMs: 320, H correctors: 288, V correctors: 288\n" - ] - } - ], - "source": [ - "bpms = SR.bpms.get(\"BPM\")\n", - "hcorr = SR.magnets.get(\"HCorr\")\n", - "vcorr = SR.magnets.get(\"VCorr\")\n", - "print(f\"BPMs: {len(bpms)}, H correctors: {len(hcorr)}, V correctors: {len(vcorr)}\")" - ] + "outputs": [], + "source": "bpms = SR.diagnostic.bpms.get(\"BPM\")\nhcorr = SR.magnets.get(\"HCorr\")\nvcorr = SR.magnets.get(\"VCorr\")\nprint(f\"BPMs: {len(bpms)}, H correctors: {len(hcorr)}, V correctors: {len(vcorr)}\")" }, { "cell_type": "markdown", diff --git a/examples/use_cases/03-orbit_correction.py b/examples/use_cases/03-orbit_correction.py index ceef8ec1..b7af039c 100644 --- a/examples/use_cases/03-orbit_correction.py +++ b/examples/use_cases/03-orbit_correction.py @@ -84,7 +84,7 @@ # In[4]: -bpms = SR.bpms.get("BPM") +bpms = SR.diagnostic.bpms.get("BPM") hcorr = SR.magnets.get("HCorr") vcorr = SR.magnets.get("VCorr") print(f"BPMs: {len(bpms)}, H correctors: {len(hcorr)}, V correctors: {len(vcorr)}") diff --git a/pyaml/arrays/bpm.py b/pyaml/arrays/bpm.py index 4ea84927..dd9d2996 100644 --- a/pyaml/arrays/bpm.py +++ b/pyaml/arrays/bpm.py @@ -88,4 +88,4 @@ def fill_array(self, holder: ElementHolder): holder : ElementHolder The element holder to populate the :py:class:`.BPMArray` with. """ - holder.bpms.add(self._name, self._elements) + holder.diagnostic.bpms.add(self._name, self._elements) diff --git a/pyaml/common/holders/diagnostic_holder.py b/pyaml/common/holders/diagnostic_holder.py index 5fb99202..361a06ed 100644 --- a/pyaml/common/holders/diagnostic_holder.py +++ b/pyaml/common/holders/diagnostic_holder.py @@ -6,6 +6,7 @@ from ...diagnostics.tune_monitor import BetatronTuneMonitor from ..element import Element, __pyaml_repr__ from ..exception import PyAMLException +from .sub_holders import BPMHolder, BPMsHolder if TYPE_CHECKING: from .element_holder import ElementHolder @@ -22,6 +23,8 @@ class DiagnosticHolder: Attributes ---------- + bpm, bpms + Single BPM by name, or a named BPM array. betatron_tune Betatron tune monitor configured as ``BETATRON_TUNE``, the default, validated against its expected class. @@ -44,6 +47,8 @@ class DiagnosticHolder: >>> measured_tune = default_tune_monitor.tune.get() >>> spare_tune_monitor = sr.live.diagnostic.get("SPARE_BETATRON_TUNE_MONITOR") >>> all_diagnostics = sr.live.diagnostic.get() + >>> bpm = sr.live.diagnostic.bpm.get("BPM01") + >>> bpms = sr.live.diagnostic.bpms.get("BPMS") """ def __init__(self, peer: "ElementHolder"): @@ -51,6 +56,18 @@ def __init__(self, peer: "ElementHolder"): Initialize a diagnostic holder for an element holder. """ self._peer = peer + self._bpm_holder = BPMHolder(peer) + self._bpms_holder = BPMsHolder(peer) + + @property + def bpm(self) -> BPMHolder: + """Return the bpm.""" + return self._bpm_holder + + @property + def bpms(self) -> BPMsHolder: + """Return the bpms.""" + return self._bpms_holder def get(self, name: str = None) -> "Element | ElementArray": """ diff --git a/pyaml/common/holders/element_holder.py b/pyaml/common/holders/element_holder.py index 934dfd41..5b97734a 100644 --- a/pyaml/common/holders/element_holder.py +++ b/pyaml/common/holders/element_holder.py @@ -20,8 +20,6 @@ from .diagnostic_holder import DiagnosticHolder from .rf_holder import RFHolder from .sub_holders import ( - BPMHolder, - BPMsHolder, CombinedFunctionMagnetHolder, CombinedFunctionMagnetsHolder, MagnetHolder, @@ -59,8 +57,6 @@ class ElementHolder(metaclass=ABCMeta): ---------- magnet, magnets Single magnet by name, or a named magnet array. - bpm, bpms - Single BPM by name, or a named BPM array. combined_function_magnet, combined_function_magnets Single combined-function magnet by name, or a named array. serialized_magnet, serialized_magnets @@ -163,8 +159,6 @@ def __init__(self): self._serialized_magnets_holder = SerializedMagnetsHolder(self) self._combined_function_magnet_holder = CombinedFunctionMagnetHolder(self) self._combined_function_magnets_holder = CombinedFunctionMagnetsHolder(self) - self._bpm_holder = BPMHolder(self) - self._bpms_holder = BPMsHolder(self) self._rf_holder = RFHolder(self) self._diagnostic_holder = DiagnosticHolder(self) self._tool_holder = ToolHolder(self) @@ -206,16 +200,6 @@ def combined_function_magnets(self) -> CombinedFunctionMagnetsHolder: """Return the combined function magnets.""" return self._combined_function_magnets_holder - @property - def bpm(self) -> BPMHolder: - """Return the bpm.""" - return self._bpm_holder - - @property - def bpms(self) -> BPMsHolder: - """Return the bpms.""" - return self._bpms_holder - @property def rf(self) -> RFHolder: """Return the rf.""" diff --git a/pyaml/common/holders/sub_holders.py b/pyaml/common/holders/sub_holders.py index d61088ba..f5e36b92 100644 --- a/pyaml/common/holders/sub_holders.py +++ b/pyaml/common/holders/sub_holders.py @@ -221,11 +221,12 @@ def __init__(self, peer: "ElementHolder"): """ Initialize the BPMsHolder. """ + bpm_holder = BPMHolder(peer) super().__init__( peer, peer._BPM_ARRAYS, - peer.bpm.all, - peer.bpm.get, + bpm_holder.all, + bpm_holder.get, BPMArray, "BPM array", ) diff --git a/pyaml/control/controlsystem.py b/pyaml/control/controlsystem.py index 9199f98d..d2e25470 100644 --- a/pyaml/control/controlsystem.py +++ b/pyaml/control/controlsystem.py @@ -248,7 +248,7 @@ def _fill_bpm(self, bpm: BPM) -> None: positions = RBpmArray(position_devices[0], position_devices[1]) tilt = RWBpmTiltScalar(tilt_devices[0]) offsets = RWBpmOffsetArray(offset_devices[0], offset_devices[1]) - self.bpm.add(bpm.attach(self, positions, offsets, tilt)) + self.diagnostic.bpm.add(bpm.attach(self, positions, offsets, tilt)) def _fill_rf_plant(self, rf_plant: RFPlant) -> None: attached_transmitters: list[RFTransmitter] = [] diff --git a/pyaml/external/pySC_interface.py b/pyaml/external/pySC_interface.py index 3be5196e..b9e60493 100644 --- a/pyaml/external/pySC_interface.py +++ b/pyaml/external/pySC_interface.py @@ -56,7 +56,7 @@ def __init__( """ self.element_holder = element_holder - self.bpm_array = element_holder.bpms.get(bpm_array_name) + self.bpm_array = element_holder.diagnostic.bpms.get(bpm_array_name) self.rf_plant_name = rf_plant_name if rf_plant_name is not None: diff --git a/pyaml/lattice/simulator.py b/pyaml/lattice/simulator.py index 10bd537f..14903724 100644 --- a/pyaml/lattice/simulator.py +++ b/pyaml/lattice/simulator.py @@ -291,7 +291,9 @@ def _fill_bpm(self, bpm: BPM) -> None: if len(bpm_elt.Offset) != 2: raise PyAMLException(f"BPM {bpm.get_name()} offset must be a 2-element array.") update_bpm_transform_matrix(bpm_elt) - self.bpm.add(bpm.attach(self, RBpmArray(bpm_elt, self.ring), RWBpmOffsetArray(bpm_elt), RWBpmTiltScalar(bpm_elt))) + self.diagnostic.bpm.add( + bpm.attach(self, RBpmArray(bpm_elt, self.ring), RWBpmOffsetArray(bpm_elt), RWBpmTiltScalar(bpm_elt)) + ) def _fill_rf_plant(self, rf_plant: RFPlant) -> None: if rf_plant.transmitters: diff --git a/pyaml/tuning_tools/bba.py b/pyaml/tuning_tools/bba.py index 10dd6cdb..d5cc32df 100644 --- a/pyaml/tuning_tools/bba.py +++ b/pyaml/tuning_tools/bba.py @@ -163,13 +163,13 @@ def __init__( def bpms(self) -> "BPMArray": """Return the BPM array used for the measurement.""" self.check_peer() - return self.peer.bpms.get(self.bpm_array_name) + return self.peer.diagnostic.bpms.get(self.bpm_array_name) @property def bpm(self) -> "BPM": """Return the reference BPM used for the alignment.""" self.check_peer() - return self.peer.bpm.get(self.bpm_name) + return self.peer.diagnostic.bpm.get(self.bpm_name) @property def hcorrector(self) -> "Magnet": diff --git a/pyaml/tuning_tools/bba2.py b/pyaml/tuning_tools/bba2.py index 25424a0c..05544687 100644 --- a/pyaml/tuning_tools/bba2.py +++ b/pyaml/tuning_tools/bba2.py @@ -244,13 +244,13 @@ def __init__( def bpms(self) -> "BPMArray": """Return the BPM array used for the measurement.""" self.check_peer() - return self.peer.bpms.get(self.bpm_array_name) + return self.peer.diagnostic.bpms.get(self.bpm_array_name) @property def bpm(self) -> "BPM": """Return the reference BPM used for the alignment.""" self.check_peer() - return self.peer.bpm.get(self.bpm_name) + return self.peer.diagnostic.bpm.get(self.bpm_name) @property def hcorrector(self) -> "Magnet": @@ -359,7 +359,7 @@ def _init_responses( quad = design.magnet.get(quadname) sth = design.magnet.get(steererhname) stv = design.magnet.get(steerervname) - orbit = design.bpms.get(bpmname).positions + orbit = design.diagnostic.bpms.get(bpmname).positions tune_design = design.get_tune_tuning(tunename) tune_live = self._peer.get_tune_tuning(tunename) diff --git a/pyaml/tuning_tools/chromaticity_monitor.py b/pyaml/tuning_tools/chromaticity_monitor.py index 0f0ad6e6..f9bfa525 100644 --- a/pyaml/tuning_tools/chromaticity_monitor.py +++ b/pyaml/tuning_tools/chromaticity_monitor.py @@ -215,7 +215,7 @@ def bpms(self) -> "BPMArray | None": self.check_peer() if self.bpm_array_name is None: return None - return self.peer.bpms.get(self.bpm_array_name) + return self.peer.diagnostic.bpms.get(self.bpm_array_name) @property def chromaticity(self) -> ReadFloatArray: diff --git a/pyaml/tuning_tools/dispersion.py b/pyaml/tuning_tools/dispersion.py index e8f8f07d..81fccb11 100644 --- a/pyaml/tuning_tools/dispersion.py +++ b/pyaml/tuning_tools/dispersion.py @@ -91,7 +91,7 @@ def __init__(self, name: str, bpm_array_name: str, rf_plant_name: str, frequency def bpms(self) -> "BPMArray": """Return the BPM array used for orbit readback.""" self.check_peer() - return self.peer.bpms.get(self.bpm_array_name) + return self.peer.diagnostic.bpms.get(self.bpm_array_name) @property def rf_plant(self) -> "RFPlant": diff --git a/pyaml/tuning_tools/orbit.py b/pyaml/tuning_tools/orbit.py index feb9713c..61468323 100644 --- a/pyaml/tuning_tools/orbit.py +++ b/pyaml/tuning_tools/orbit.py @@ -212,7 +212,7 @@ def response_matrix(self) -> OrbitResponseMatrixData | None: def bpms(self) -> BPMArray: """Return the BPM array used for orbit readback.""" self.check_peer() - return self.peer.bpms.get(self.bpm_array_name) + return self.peer.diagnostic.bpms.get(self.bpm_array_name) @property def hcorrectors(self) -> MagnetArray: diff --git a/pyaml/tuning_tools/orbit_response_matrix.py b/pyaml/tuning_tools/orbit_response_matrix.py index 7d4964cc..101a7220 100644 --- a/pyaml/tuning_tools/orbit_response_matrix.py +++ b/pyaml/tuning_tools/orbit_response_matrix.py @@ -141,7 +141,7 @@ def __init__( def bpms(self) -> "BPMArray": """Return the BPM array used for orbit readback.""" self.check_peer() - return self.peer.bpms.get(self.bpm_array_name) + return self.peer.diagnostic.bpms.get(self.bpm_array_name) @property def hcorrectors(self) -> "MagnetArray": diff --git a/tests/arrays/test_arrays.py b/tests/arrays/test_arrays.py index 7ae9b871..62f9c202 100644 --- a/tests/arrays/test_arrays.py +++ b/tests/arrays/test_arrays.py @@ -128,32 +128,32 @@ def test_arrays(install_test_package): # Test BPMs array # Using aggregator - pos = sr.design.bpms.get("BPMS").positions.get() + pos = sr.design.diagnostic.bpms.get("BPMS").positions.get() assert np.abs(pos[0][0] - 7.22262850488348e-05) < 1e-10 assert np.abs(pos[0][1] - 3.4291613955705856e-05) < 1e-10 assert np.abs(pos[1][0] + 1.1696152238807462e-04) < 1e-10 assert np.abs(pos[1][1] - 7.4265634524358045e-06) < 1e-10 # Using aggregator (h and v) - pos_h = sr.design.bpms.get("BPMS").h.get() - pos_v = sr.design.bpms.get("BPMS").v.get() + pos_h = sr.design.diagnostic.bpms.get("BPMS").h.get() + pos_v = sr.design.diagnostic.bpms.get("BPMS").v.get() assert np.all(np.isclose(pos[:, 0], pos_h, rtol=1e-15, atol=1e-15)) assert np.all(np.isclose(pos[:, 1], pos_v, rtol=1e-15, atol=1e-15)) # Test BPM transformation matrices - sr.design.bpm.get("BPM_C04-01").offset.set([0.1, 0.2]) - sr.design.bpm.get("BPM_C04-02").offset.set([0.3, 0.4]) - pos = sr.design.bpms.get("BPMS").positions.get() + sr.design.diagnostic.bpm.get("BPM_C04-01").offset.set([0.1, 0.2]) + sr.design.diagnostic.bpm.get("BPM_C04-02").offset.set([0.3, 0.4]) + pos = sr.design.diagnostic.bpms.get("BPMS").positions.get() assert np.abs(pos[0][0] - 7.22262850488348e-05 - 0.1) < 1e-10 assert np.abs(pos[0][1] - 3.4291613955705856e-05 - 0.2) < 1e-10 assert np.abs(pos[1][0] + 1.1696152238807462e-04 - 0.3) < 1e-10 assert np.abs(pos[1][1] - 7.4265634524358045e-06 - 0.4) < 1e-10 - sr.design.bpm.get("BPM_C04-01").offset.set([0.0, 0.0]) - sr.design.bpm.get("BPM_C04-02").offset.set([0.0, 0.0]) + sr.design.diagnostic.bpm.get("BPM_C04-01").offset.set([0.0, 0.0]) + sr.design.diagnostic.bpm.get("BPM_C04-02").offset.set([0.0, 0.0]) # No aggregator bpms = [] - for b in sr.design.bpms.get("BPMS"): + for b in sr.design.diagnostic.bpms.get("BPMS"): bpms.append(b) bpms = BPMArray("BPM_noagg", bpms, use_aggregator=False) @@ -175,7 +175,7 @@ def test_arrays(install_test_package): assert len(allMags) == 7 # Create an array that contains all BPM - allBpms = BPMArray("AllBPMs", sr.design.bpm.all()) + allBpms = BPMArray("AllBPMs", sr.design.diagnostic.bpm.all()) assert len(allBpms) == 2 cfm = sr.design.combined_function_magnets.get("CFM") @@ -204,7 +204,7 @@ def test_arrays(install_test_package): assert np.abs(strHVSQ[4] + 0.000017) < 1e-10 # V assert np.abs(strHVSQ[5] - 1e-6) < 1e-10 # SQ - bpmsLive = BPMArray("", sr.live.bpm.all()) + bpmsLive = BPMArray("", sr.live.diagnostic.bpm.all()) bpmsLive.positions.get() # Test dynamic arrays @@ -232,7 +232,7 @@ def test_arrays(install_test_package): emptyBPM = BPM(name="emptyBPM", elements=[]) emptyBPM.fill_array(sr.design) # Attach the array - v = sr.design.bpms.get("emptyBPM").positions.get() # Ensure good attach + v = sr.design.diagnostic.bpms.get("emptyBPM").positions.get() # Ensure good attach assert np.shape(v) == (0,) emptyCFM = CombinedFunctionMagnet(name="emptyCFM", elements=[]) diff --git a/tests/bpm/test_bpm.py b/tests/bpm/test_bpm.py index 93ac68ec..3bf8ee71 100644 --- a/tests/bpm/test_bpm.py +++ b/tests/bpm/test_bpm.py @@ -9,7 +9,7 @@ def test_simulator_bpm_tilt(): sr.design.get_lattice().disable_6d() sr.design.magnet.get("SH1A-C01-H").strength.set(10e-6) # Add orbit sr.design.magnet.get("SH1A-C01-V").strength.set(10e-6) # Add orbit - bpm = sr.design.bpm.get("BPM_C01-01") + bpm = sr.design.diagnostic.bpm.get("BPM_C01-01") assert np.allclose(bpm.positions.get(), np.array([5.90809968e-05, 2.24832853e-05])) assert bpm.tilt.get() == 0 alpha = np.pi / 3 @@ -30,7 +30,7 @@ def test_simulator_bpm_tilt(): def test_simulator_bpm_offset(): sr: Accelerator = Accelerator.load("tests/config/bpms.yaml", ignore_external=True) sr.design.get_lattice().disable_6d() - bpm = sr.design.bpm.get("BPM_C01-01") + bpm = sr.design.diagnostic.bpm.get("BPM_C01-01") assert bpm.offset.get()[0] == 0 assert bpm.offset.get()[1] == 0 @@ -48,8 +48,8 @@ def test_simulator_bpm_offset(): def test_simulator_bpm_position(install_test_package): sr: Accelerator = Accelerator.load("tests/config/bpms.yaml") sr.design.get_lattice().disable_6d() - bpm = sr.design.bpm.get("BPM_C01-01") - bpm_simple = sr.live.bpm.get("BPM_C01-02") + bpm = sr.design.diagnostic.bpm.get("BPM_C01-01") + bpm_simple = sr.live.diagnostic.bpm.get("BPM_C01-02") assert np.allclose(bpm.positions.get(), np.array([0.0, 0.0])) assert np.allclose(bpm_simple.positions.get(), np.array([0.0, 0.0])) @@ -58,9 +58,9 @@ def test_simulator_bpm_position(install_test_package): def test_simulator_bpm_position_with_bad_corrector_strength(): sr: Accelerator = Accelerator.load("tests/config/bpms.yaml", ignore_external=True) sr.design.get_lattice().disable_6d() - bpm1 = sr.design.bpm.get("BPM_C01-01") - bpm_simple = sr.design.bpm.get("BPM_C01-02") - bpm3 = sr.design.bpm.get("BPM_C01-03") + bpm1 = sr.design.diagnostic.bpm.get("BPM_C01-01") + bpm_simple = sr.design.diagnostic.bpm.get("BPM_C01-02") + bpm3 = sr.design.diagnostic.bpm.get("BPM_C01-03") sr.design.magnet.get("SH1A-C01-H").strength.set(-1e-6) sr.design.magnet.get("SH1A-C01-V").strength.set(-1e-6) diff --git a/tests/bpm/test_bpm_controlsystem.py b/tests/bpm/test_bpm_controlsystem.py index 13321ca1..8b534536 100644 --- a/tests/bpm/test_bpm_controlsystem.py +++ b/tests/bpm/test_bpm_controlsystem.py @@ -11,7 +11,7 @@ ) def test_controlsystem_bpm_tilt(install_test_package): sr: Accelerator = Accelerator.load("tests/config/bpms.yaml") - bpm = sr.live.bpm.get("BPM_C01-01") + bpm = sr.live.diagnostic.bpm.get("BPM_C01-01") print(bpm.tilt.get()) assert bpm.tilt.get() == 0 @@ -26,7 +26,7 @@ def test_controlsystem_bpm_tilt(install_test_package): ) def test_controlsystem_bpm_offset(install_test_package): sr: Accelerator = Accelerator.load("tests/config/bpms.yaml") - bpm = sr.live.bpm.get("BPM_C01-01") + bpm = sr.live.diagnostic.bpm.get("BPM_C01-01") assert bpm.offset.get()[0] == 0 assert bpm.offset.get()[1] == 0 @@ -43,8 +43,8 @@ def test_controlsystem_bpm_offset(install_test_package): ) def test_controlsystem_bpm_position(install_test_package): sr: Accelerator = Accelerator.load("tests/config/bpms.yaml") - bpm = sr.live.bpm.get("BPM_C01-01") - bpm_simple = sr.live.bpm.get("BPM_C01-02") + bpm = sr.live.diagnostic.bpm.get("BPM_C01-01") + bpm_simple = sr.live.diagnostic.bpm.get("BPM_C01-02") assert np.allclose(bpm.positions.get(), np.array([0.0, 0.0])) assert np.allclose(bpm_simple.positions.get(), np.array([0.0, 0.0])) @@ -91,6 +91,6 @@ def test_controlsystem_bpm_position_indexed(install_test_package): set_attribute("srdiag/bpm/c01-04/Position", [0.0, 1.0], unit="mm") sr: Accelerator = Accelerator.load("tests/config/bpms.yaml") - bpm = sr.live.bpm.get("BPM_C01-04") + bpm = sr.live.diagnostic.bpm.get("BPM_C01-04") assert np.allclose(bpm.positions.get(), np.array([0.0, 1.0])) diff --git a/tests/common/test_array_holder_navigation.py b/tests/common/test_array_holder_navigation.py index 3fdcbe42..7e630268 100644 --- a/tests/common/test_array_holder_navigation.py +++ b/tests/common/test_array_holder_navigation.py @@ -28,7 +28,7 @@ def test_dynamic_attribute_returns_named_combined_function_magnet_array(holder): def test_dynamic_attribute_returns_named_bpm_array(holder): - assert holder.bpms.BPMS is holder.bpms.get("BPMS") + assert holder.diagnostic.bpms.BPMS is holder.diagnostic.bpms.get("BPMS") def test_dynamic_attribute_returns_named_serialized_magnet_array(serialized_holder): @@ -60,7 +60,7 @@ def test_dynamic_attribute_rejects_non_identifier_names(holder): def test_dir_includes_configured_array_names(holder): assert {"HCORR", "VCORR", "HVCORR"} <= set(dir(holder.magnets)) assert "CFM" in dir(holder.combined_function_magnets) - assert "BPMS" in dir(holder.bpms) + assert "BPMS" in dir(holder.diagnostic.bpms) def test_dir_excludes_non_identifier_array_names(holder): diff --git a/tests/common/test_element_holder_collection.py b/tests/common/test_element_holder_collection.py index 937d2da7..4a55fadf 100644 --- a/tests/common/test_element_holder_collection.py +++ b/tests/common/test_element_holder_collection.py @@ -16,7 +16,7 @@ def holder(accelerator_from_fragments, sr_configuration_fragments): def test_exact_name_returns_the_element_or_none(holder): - assert holder["BPM_C04-01"] is holder.bpm.get("BPM_C04-01") + assert holder["BPM_C04-01"] is holder.diagnostic.bpm.get("BPM_C04-01") assert holder["UNKNOWN"] is None @@ -47,7 +47,7 @@ def test_new_calls_reflect_registry_additions(holder): assert "EXTRA_BPM" not in previous.names() assert holder.get().names() == previous.names() + ["EXTRA_BPM"] - assert holder["EXTRA_BPM"] is holder.bpm.get("EXTRA_BPM") + assert holder["EXTRA_BPM"] is holder.diagnostic.bpm.get("EXTRA_BPM") def test_patterns_always_return_arrays(holder): @@ -70,7 +70,7 @@ def test_character_classes_in_name_patterns(holder): def test_colons_are_part_of_names(holder): holder.fill_device([BPM("CELL04:BPM01", lattice_names="list(BPM_C04-01)")]) - assert holder["CELL04:BPM01"] is holder.bpm.get("CELL04:BPM01") + assert holder["CELL04:BPM01"] is holder.diagnostic.bpm.get("CELL04:BPM01") assert holder["CELL04:BPM*"].names() == ["CELL04:BPM01"] assert holder["model_name:*"].names() == [] diff --git a/tests/common/test_errors.py b/tests/common/test_errors.py index 38ce79f5..4265a20c 100644 --- a/tests/common/test_errors.py +++ b/tests/common/test_errors.py @@ -43,7 +43,7 @@ def test_tune(install_test_package): assert "Magnet QF1A-C05XX not defined" in str(exc.value) with pytest.raises(PyAMLException) as exc: - m2 = sr.design.bpm.get("QF1A-C05XX") + m2 = sr.design.diagnostic.bpm.get("QF1A-C05XX") assert "BPM QF1A-C05XX not defined" in str(exc.value) diff --git a/tests/diagnostics/test_diagnostic_accessors.py b/tests/diagnostics/test_diagnostic_accessors.py index 7e878914..2247db71 100644 --- a/tests/diagnostics/test_diagnostic_accessors.py +++ b/tests/diagnostics/test_diagnostic_accessors.py @@ -58,3 +58,24 @@ def test_diagnostic_raises_when_default_wrong_type(): _ = design.diagnostic.betatron_tune assert "BETATRON_TUNE" in str(exc.value) assert "BetatronTuneMonitor" in str(exc.value) + + +def test_diagnostic_bpm_returns_named_bpm(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + assert design.diagnostic.bpm.get("BPM_C04-04").get_name() == "BPM_C04-04" + + +def test_diagnostic_bpms_returns_named_array(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + bpms = design.diagnostic.bpms.get("BPM") + assert design.diagnostic.bpm.get("BPM_C04-04") in bpms diff --git a/tests/integration/test_dt4acc_twin_smoke.py b/tests/integration/test_dt4acc_twin_smoke.py index 549bdb4e..44220e09 100644 --- a/tests/integration/test_dt4acc_twin_smoke.py +++ b/tests/integration/test_dt4acc_twin_smoke.py @@ -134,7 +134,7 @@ def deactivated_test_orbit_correction(config_key: str): try: accelerator = _build_accelerator(config_key) control_mode = accelerator.live - bpms = control_mode.bpms.get("bpms") + bpms = control_mode.diagnostic.bpms.get("bpms") orbit_response_matrix = control_mode.get_orm_tuning("DEFAULT_ORBIT_RESPONSE_MATRIX") orbit_correction = control_mode.get_orbit_tuning("DEFAULT_ORBIT_CORRECTION") orbit_response_matrix.measure() diff --git a/tests/test_accelerator_load.py b/tests/test_accelerator_load.py index f30a68e3..0bc649ce 100644 --- a/tests/test_accelerator_load.py +++ b/tests/test_accelerator_load.py @@ -19,8 +19,8 @@ def test_peer(): def test_repr_is_informative_and_bounded(): sr = Accelerator.load("tests/config/EBSOrbit.yaml") - bpm = sr.design.bpm.get("BPM_C04-04") - bpms = sr.design.bpms.get("BPM") + bpm = sr.design.diagnostic.bpm.get("BPM_C04-04") + bpms = sr.design.diagnostic.bpms.get("BPM") assert repr(bpm) == ( "BPM(name='BPM_C04-04', lattice_names='BPM_C04-04', " @@ -46,7 +46,7 @@ def test_repr_options_limit_sequences(): set_repr_options(max_items=1) sr = Accelerator.load("tests/config/EBSOrbit.yaml") - bpms = sr.design.bpms.get("BPM") + bpms = sr.design.diagnostic.bpms.get("BPM") assert f"... +{len(bpms) - 1} more ..." in repr(bpms) finally: set_repr_options( diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index 828627aa..43627e1d 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -59,7 +59,7 @@ def test_inline_catalog_is_supported(install_test_package): } ) - bpm = sr.live.bpm.get("BPM_C02-01") + bpm = sr.live.diagnostic.bpm.get("BPM_C02-01") assert np.allclose(bpm.positions.get(), np.array([0.0, 0.0])) @@ -184,7 +184,7 @@ def test_indexed_catalog_entry_extracts_scalar_from_vector_attribute(install_tes } ) - bpm = sr.live.bpm.get("BPM_TEST") + bpm = sr.live.diagnostic.bpm.get("BPM_TEST") positions = bpm.positions.get() assert np.isclose(positions[0], 1.5) assert np.isclose(positions[1], -0.3) diff --git a/tests/test_load_conf_with_code.py b/tests/test_load_conf_with_code.py index e65f5be5..25a98a8d 100644 --- a/tests/test_load_conf_with_code.py +++ b/tests/test_load_conf_with_code.py @@ -8,13 +8,13 @@ def test_load_conf_with_code(): config_path = parent_folder.joinpath("config", "EBSOrbit.yaml").resolve() sr: Accelerator = Accelerator.load(config_path) - bpms = sr.live.bpms.get("BPM") + bpms = sr.live.diagnostic.bpms.get("BPM") assert bpms is not None assert len(bpms) == 320 assert sr.live[bpms[0].get_name()] is bpms[0] assert sr.live["BPM*"].names() == bpms.names() assert sr.live[:].names() == [element.get_name() for element in sr.live.get_all_elements()] - assert sr.design["BPM*"].names() == sr.design.bpms.get("BPM").names() + assert sr.design["BPM*"].names() == sr.design.diagnostic.bpms.get("BPM").names() - assert sr.live.bpms.BPM is bpms + assert sr.live.diagnostic.bpms.BPM is bpms diff --git a/tests/tuning_tools/test_bba.py b/tests/tuning_tools/test_bba.py index 330c8118..f4247103 100644 --- a/tests/tuning_tools/test_bba.py +++ b/tests/tuning_tools/test_bba.py @@ -9,7 +9,7 @@ def test_bba(): SR = sr.design # Add a misalignement - SR.bpm.get("BPM_C04-04").offset.set([20e-6, -15e-6]) + SR.diagnostic.bpm.get("BPM_C04-04").offset.set([20e-6, -15e-6]) # BBA (standard bow tie, model independant) bba = SR.get_bba("BBA-BPM_C04-04") diff --git a/tests/tuning_tools/test_tool_accessors.py b/tests/tuning_tools/test_tool_accessors.py index a3dcef13..d9a54e7b 100644 --- a/tests/tuning_tools/test_tool_accessors.py +++ b/tests/tuning_tools/test_tool_accessors.py @@ -27,7 +27,7 @@ def test_tuning_tools_expose_configured_elements(): assert design.crm.chromaticity_monitor is chromaticity_monitor assert design.crm.sextupoles is sextupoles - bpms = design.bpms.get("BPM") + bpms = design.diagnostic.bpms.get("BPM") rf_plant = design.rf.get("DEFAULT_RF_PLANT") assert chromaticity_monitor.tune_monitor is tune_monitor assert chromaticity_monitor.bpms is bpms @@ -49,7 +49,7 @@ def test_tuning_tools_expose_configured_elements(): for name in ("BBA-BPM_C04-04", "BBA2-BPM_C04-04"): bba = design.get_bba(name) assert bba.bpms is bpms - assert bba.bpm is design.bpm.get("BPM_C04-04") + assert bba.bpm is design.diagnostic.bpm.get("BPM_C04-04") assert bba.hcorrector is design.magnet.get("SF2E-C02-H") assert bba.vcorrector is design.magnet.get("SD1A-C26-V") assert bba.quadrupole is design.magnet.get("QF6B-C04") diff --git a/tests/tuning_tools/test_tuning_dispersion.py b/tests/tuning_tools/test_tuning_dispersion.py index 16d2cd39..9cd3e66b 100644 --- a/tests/tuning_tools/test_tuning_dispersion.py +++ b/tests/tuning_tools/test_tuning_dispersion.py @@ -17,7 +17,7 @@ def test_tuning_orm(): dispersion.measure() dispersion_data = dispersion.get() - bpms = element_holder.bpms.get("BPM") + bpms = element_holder.diagnostic.bpms.get("BPM") assert len(dispersion_data["frequency_response_x"]) == len(bpms) assert len(dispersion_data["frequency_response_y"]) == len(bpms) diff --git a/tests/tuning_tools/test_tuning_orbit_correction.py b/tests/tuning_tools/test_tuning_orbit_correction.py index 4bf3b6a6..7cf8fb56 100644 --- a/tests/tuning_tools/test_tuning_orbit_correction.py +++ b/tests/tuning_tools/test_tuning_orbit_correction.py @@ -22,7 +22,7 @@ def test_tuning_orbit_correction(): std_kick = 1e-6 hcorr = element_holder.magnets.get("HCorr") vcorr = element_holder.magnets.get("VCorr") - bpms = element_holder.bpms.get("BPM") + bpms = element_holder.diagnostic.bpms.get("BPM") x, y = bpms.positions.get().T # get reference orbit reference = np.concat((x, y)) diff --git a/tests/tuning_tools/test_tuning_orm.py b/tests/tuning_tools/test_tuning_orm.py index 553bce16..f3652f0c 100644 --- a/tests/tuning_tools/test_tuning_orm.py +++ b/tests/tuning_tools/test_tuning_orm.py @@ -17,7 +17,7 @@ def test_tuning_orm(): orm = element_holder.orm - bpms = element_holder.bpms.get("BPM") + bpms = element_holder.diagnostic.bpms.get("BPM") hcorr_names = element_holder.magnets.get("HCorr").names()[:4] vcorr_names = element_holder.magnets.get("VCorr").names()[:4] orm.measure(corrector_names=hcorr_names + vcorr_names) From 895d6517b934b17a5365e0f0dd4ed2e9572f447b Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Fri, 18 Sep 2026 17:09:57 +0200 Subject: [PATCH 17/49] Unify name/wildcard/regex lookup and miss-behavior across holders --- pyaml/arrays/element_array.py | 55 +++++---- pyaml/common/holders/diagnostic_holder.py | 32 +++++ pyaml/common/holders/element_holder.py | 84 ++++++++----- pyaml/common/holders/generic_array_holder.py | 30 ++++- .../common/holders/generic_element_holder.py | 33 ++++++ pyaml/common/holders/rf_holder.py | 64 ++++++++++ pyaml/common/holders/tool_holder.py | 32 +++++ tests/arrays/test_array_selection_types.py | 39 ++++++ tests/common/test_array_holder_navigation.py | 57 +++++++++ .../common/test_element_holder_collection.py | 112 +++++++++++++++++- tests/common/test_name_matching.py | 74 ++++++++++++ .../diagnostics/test_diagnostic_accessors.py | 68 +++++++++++ tests/rf/test_rf_holder.py | 48 ++++++++ tests/tuning_tools/test_tool_holder.py | 38 ++++++ 14 files changed, 702 insertions(+), 64 deletions(-) create mode 100644 tests/common/test_name_matching.py create mode 100644 tests/rf/test_rf_holder.py create mode 100644 tests/tuning_tools/test_tool_holder.py diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index 26b7ce26..ffa9e3d9 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -13,6 +13,7 @@ from ..bpm.bpm import BPM from ..common.element import Element, __pyaml_repr__ from ..common.exception import PyAMLException +from ..common.name_matching import resolve_names from ..magnet.cfm_magnet import CombinedFunctionMagnet from ..magnet.magnet import Magnet from ..magnet.serialized_magnet import SerializedMagnets @@ -240,22 +241,6 @@ def mro_as_list(cls: type) -> list[type]: return self.__create_array("", chosen, elements) - def _select_names(self, pattern: str) -> "ElementArray": - """Select names without interpreting field selectors. - - Parameters - ---------- - pattern : str - A fnmatch pattern applied to each element name. - - Returns - ------- - ElementArray - Typed selection in the original order, including an empty array - when no names match. - """ - return self._typed_array([element for element in self if fnmatch.fnmatch(element.get_name(), pattern)]) - def __is_bool_mask(self, other: object) -> bool: """Return True if 'other' looks like a boolean mask (list or numpy array).""" # --- numpy boolean array --- @@ -576,8 +561,13 @@ def __getitem__(self, key): Parameters ---------- - key : int, slice or str - Element index, slice, name pattern, or existing field selector. + key : int, slice, str, list[str] or tuple[str, ...] + Element index, slice, name pattern (or ``field:pattern`` field + selector), or a list/tuple of name patterns. A name pattern is a + literal name (must match an element in this array), an fnmatch + wildcard (``*``, ``?`` or ``[``), or a ``re:``-prefixed regular + expression. A list or tuple resolves each entry independently + and unions the results. Returns ------- @@ -585,33 +575,46 @@ def __getitem__(self, key): Indexed element or an array inferred from all selected elements. Different Magnet subclasses produce a MagnetArray; mixed element families produce an ElementArray. Empty selections return an - empty ElementArray. + empty ElementArray, except a literal name pattern (or literal + entry within a list/tuple) matching nothing, which raises. + + Raises + ------ + PyAMLException + If a literal name pattern, or a literal entry within a list or + tuple, matches no element in this array, or a ``re:`` pattern is + not a valid regular expression. Examples -------- >>> magnets = sr.design.magnets.get() >>> subset = magnets[:] # MagnetArray, including mixed magnet classes >>> correctors = magnets["SH*"] # MagnetArray + >>> correctors = magnets["re:^SH1A-C0[12]-H$"] # MagnetArray + >>> selection = magnets[["SH1A-C01-H", "SH*-V"]] # Union of patterns """ if isinstance(key, slice): # Slicing r = super().__getitem__(key) + elif isinstance(key, (list, tuple)): + # Selection by a list/tuple of name patterns + matched = set(resolve_names([e.get_name() for e in self], key)) + r = [e for e in self if e.get_name() in matched] + elif isinstance(key, str): - fields = key.split(":") + fields = [] if key.startswith("re:") else key.split(":") if len(fields) <= 1: - # Selection by name - r = [] - for e in self: - if fnmatch.fnmatch(e.get_name(), key): - r.append(e) + # Selection by name pattern + matched = set(resolve_names([e.get_name() for e in self], key)) + r = [e for e in self if e.get_name() in matched] else: # Selection by fields r = [] for e in self: txt = self.__eval_field(fields[0], e) - if fnmatch.fnmatch(txt, fields[1]): + if fnmatch.fnmatchcase(txt, fields[1]): r.append(e) else: diff --git a/pyaml/common/holders/diagnostic_holder.py b/pyaml/common/holders/diagnostic_holder.py index 361a06ed..66213bda 100644 --- a/pyaml/common/holders/diagnostic_holder.py +++ b/pyaml/common/holders/diagnostic_holder.py @@ -6,6 +6,7 @@ from ...diagnostics.tune_monitor import BetatronTuneMonitor from ..element import Element, __pyaml_repr__ from ..exception import PyAMLException +from ..name_matching import is_wildcard, resolve_names from .sub_holders import BPMHolder, BPMsHolder if TYPE_CHECKING: @@ -95,6 +96,37 @@ def get(self, name: str = None) -> "Element | ElementArray": return ElementArray("", list(self._peer._DIAG.values())) return self._peer._get_diagnostic(name) + def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "Element | ElementArray": + """ + Return a diagnostic, or a selection typed as a generic ElementArray. + + Parameters + ---------- + key : str, list[str] or tuple[str, ...] + An exact literal name returns the stored diagnostic. An fnmatch + wildcard, a ``re:``-prefixed regular expression, or a list/tuple + of such patterns returns an ElementArray of matches (possibly + empty). + + Returns + ------- + Element or ElementArray + The stored diagnostic for an exact literal name, otherwise an + ElementArray of matches. + + Raises + ------ + PyAMLException + If an exact literal name, or a literal entry within a list or + tuple, does not match any diagnostic, or a ``re:`` pattern is + not a valid regular expression. + """ + store = self._peer._DIAG + if isinstance(key, str) and not key.startswith("re:") and not is_wildcard(key): + return self._peer._get_diagnostic(key) + names = resolve_names(store.keys(), key, what="Diagnostic") + return ElementArray("", [store[n] for n in names]) + @property def betatron_tune(self) -> BetatronTuneMonitor: """ diff --git a/pyaml/common/holders/element_holder.py b/pyaml/common/holders/element_holder.py index 5b97734a..07751ee7 100644 --- a/pyaml/common/holders/element_holder.py +++ b/pyaml/common/holders/element_holder.py @@ -1,7 +1,5 @@ """Store, resolve, and group elements shared by runtime backends.""" -import fnmatch -import re from abc import ABCMeta, abstractmethod from typing import TYPE_CHECKING, overload @@ -17,6 +15,7 @@ from ..abstract_aggregator import ScalarAggregator from ..element import Element from ..exception import PyAMLException +from ..name_matching import is_wildcard, resolve_names from .diagnostic_holder import DiagnosticHolder from .rf_holder import RFHolder from .sub_holders import ( @@ -311,29 +310,31 @@ def create_bpm_aggregators(self, bpms: list[BPM]) -> list[ScalarAggregator | Non # Elements - def find_elements(self, filter: str) -> list[str]: + def find_elements(self, filter: str | list[str] | tuple[str, ...]) -> list[str]: """ - Find element names matching a literal, wildcard, or regular expression. + Find element names matching one or several literal, wildcard, or regex patterns. Parameters ---------- - filter : str - Pattern to match. Prefix with ``re:`` for a regular expression. + filter : str, list[str] or tuple[str, ...] + Pattern, or patterns, to match. A pattern is a literal name, an + fnmatch wildcard (``*``, ``?`` or ``[`` anywhere in the string), + or a regular expression prefixed with ``re:``. Several patterns + are resolved independently and unioned, de-duplicated, in + first-encounter order. Returns ------- list[str] Matching element names. - """ - if filter.startswith("re:"): - pattern = re.compile(rf"{filter[3:]}") - elements = [k for k in self._ALL.keys() if pattern.fullmatch(k)] - elif "*" in filter or "?" in filter: - elements = [k for k in self._ALL.keys() if fnmatch.fnmatch(k, filter)] - else: - elements = [filter] - return elements + Raises + ------ + PyAMLException + If a literal pattern matches no element, or a ``re:`` pattern is + not a valid regular expression. + """ + return resolve_names(self._ALL.keys(), filter, what="Element") def _fill_array( self, @@ -450,32 +451,43 @@ def __getitem__(self, key: int) -> Element: ... def __getitem__(self, key: slice) -> ElementArray: ... @overload - def __getitem__(self, key: str) -> Element | ElementArray | None: ... + def __getitem__(self, key: str) -> Element: ... + + @overload + def __getitem__(self, key: list[str] | tuple[str, ...]) -> ElementArray: ... - def __getitem__(self, key: int | slice | str) -> Element | ElementArray | None: + def __getitem__(self, key: int | slice | str | list[str] | tuple[str, ...]) -> Element | ElementArray: """Retrieve an element or select a collection. Parameters ---------- - key : int, slice or str - Index in registration order, slice, exact name, or name pattern. - Strings containing ``*``, ``?`` or ``[`` use fnmatch matching. - Other strings are exact registry keys. Colons are literal. + key : int, slice, str, list[str] or tuple[str, ...] + Index in registration order, slice, exact name, name pattern, or + a list/tuple of patterns. Strings containing ``*``, ``?`` or + ``[`` use fnmatch matching; a ``re:`` prefix uses a regular + expression instead. Any other string is an exact registry key. + Colons are literal. A list or tuple resolves each entry + independently and unions the results. Returns ------- - Element or ElementArray or None - An index returns an element. An exact name returns its element - or None. Patterns and slices return the most specific compatible - array, or an empty ElementArray when nothing matches. + Element or ElementArray + An index or an exact literal name returns its element. Patterns, + lists/tuples of patterns, and slices return the most specific + compatible array, or an empty ElementArray when nothing matches. The full slice ``[:]`` returns a generic ElementArray, like get(). Raises ------ + PyAMLException + If an exact literal name, or a literal entry within a list or + tuple, does not match any registered element, or if a ``re:`` + pattern is not a valid regular expression. IndexError If the index is out of bounds. TypeError - If the key is neither an integer, a slice, nor a string. + If the key is neither an integer, a slice, a string, nor a + list/tuple of strings. ValueError If a slice has a zero step. @@ -483,25 +495,33 @@ def __getitem__(self, key: int | slice | str) -> Element | ElementArray | None: ----- Indices follow insertion order, not necessarily lattice order. Collections share element references but do not modify the registry. - Field filters and regular expressions are not interpreted here. + Field filters are not interpreted here. Examples -------- >>> bpm = sr.live["BPM01"] - >>> missing = sr.live["UNKNOWN"] # None + >>> missing = sr.live["UNKNOWN"] # raises PyAMLException >>> bpms = sr.live["BPM*"] >>> bpms = sr.live["BPM0[123]"] # BPM01, BPM02 or BPM03 >>> bpms = sr.live["BPM0[1-3]"] # Same selection using a range >>> quads = sr.live["Q[FD]*"] # Names starting with QF or QD >>> bpms = sr.live["BPM0[!3]"] # One character after BPM0, except 3 + >>> bpms = sr.live["re:^BPM0[12]$"] # Regular expression + >>> mixed = sr.live[["BPM01", "QF1*"]] # Union of several patterns >>> first = sr.live[0] >>> subset = sr.live[1:10] >>> all_elements = sr.live[:] """ if isinstance(key, str): - if any(marker in key for marker in "*?["): - return self.get()._select_names(key) - return self._ALL.get(key) + if key.startswith("re:") or is_wildcard(key): + names = resolve_names(self._ALL.keys(), key) + return self.get()._typed_array([self._ALL[n] for n in names]) + if key not in self._ALL: + raise PyAMLException(f"Element {key} not defined") + return self._ALL[key] + if isinstance(key, (list, tuple)): + names = resolve_names(self._ALL.keys(), key) + return self.get()._typed_array([self._ALL[n] for n in names]) if isinstance(key, int): return list(self._ALL.values())[key] if isinstance(key, slice): @@ -509,7 +529,7 @@ def __getitem__(self, key: int | slice | str) -> Element | ElementArray | None: if key == slice(None): return elements return elements._typed_array(list(elements)[key]) - raise TypeError("ElementHolder keys must be integers, slices or strings") + raise TypeError("ElementHolder keys must be integers, slices, strings, or lists/tuples of strings") def fill_element_array(self, arrayName: str, elementNames: list[str]): """ diff --git a/pyaml/common/holders/generic_array_holder.py b/pyaml/common/holders/generic_array_holder.py index 4004a52a..50fec2c1 100644 --- a/pyaml/common/holders/generic_array_holder.py +++ b/pyaml/common/holders/generic_array_holder.py @@ -110,17 +110,41 @@ def add(self, arrayName: str, elementNames: list[str]): def __getitem__(self, key): """ - Return an element from the aggregate array by index. + Select from the aggregate array of every individual element. + + Delegates to :meth:`ElementArray.__getitem__ + ` on ``self.get()`` + (the array of every individual element of this type), so ``key`` + matches against **individual element names**, not against the + registered array/family names that :meth:`get` searches. These are + deliberately two different, non-overlapping namespaces: ``get(name)`` + looks up a configured family (e.g. ``"QForTune"``), while ``[key]`` + looks up the elements themselves (e.g. ``"QF1A-C01"`` or ``"QF1*"``). Parameters ---------- - key : int or slice - Index or slice passed to the aggregate array. + key : int, slice, str, list[str] or tuple[str, ...] + Index or slice into the aggregate array, an individual element's + exact name, an fnmatch wildcard or ``re:`` regular expression + over element names, or a list/tuple of such patterns. Returns ------- object Element or sub-array selected by ``key``. + + Raises + ------ + PyAMLException + If ``key`` is an exact literal element name (or a literal entry + within a list or tuple) that matches no individual element, or a + ``re:`` pattern is not a valid regular expression. + + Examples + -------- + >>> family = sr.live.magnets.get("QForTune") # array-name namespace + >>> one_magnet = sr.live.magnets["QF1A-C01"] # element-name namespace + >>> some_magnets = sr.live.magnets["QF1*"] # element-name namespace """ return self.get().__getitem__(key) diff --git a/pyaml/common/holders/generic_element_holder.py b/pyaml/common/holders/generic_element_holder.py index a23dec8b..ce5b85a4 100644 --- a/pyaml/common/holders/generic_element_holder.py +++ b/pyaml/common/holders/generic_element_holder.py @@ -2,7 +2,9 @@ from typing import TYPE_CHECKING, Generic, TypeVar +from ...arrays.element_array import ElementArray from ..element import Element, __pyaml_repr__ +from ..name_matching import is_wildcard, resolve_names if TYPE_CHECKING: from .element_holder import ElementHolder @@ -73,6 +75,37 @@ def get(self, name: str) -> T: """ return self._peer._get(self._what, name, self._store) + def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "T | ElementArray": + """ + Return an element, or a selection typed as a generic ElementArray. + + Parameters + ---------- + key : str, list[str] or tuple[str, ...] + An exact literal name returns the stored element. An fnmatch + wildcard (``*``, ``?`` or ``[``), a ``re:``-prefixed regular + expression, or a list/tuple of such patterns returns an + :class:`~pyaml.arrays.element_array.ElementArray` of matches + (possibly empty). + + Returns + ------- + T or ElementArray + The stored element for an exact literal name, otherwise an + ElementArray of matches. + + Raises + ------ + PyAMLException + If an exact literal name, or a literal entry within a list or + tuple, does not match any element in this holder, or a ``re:`` + pattern is not a valid regular expression. + """ + if isinstance(key, str) and not key.startswith("re:") and not is_wildcard(key): + return self._peer._get(self._what, key, self._store) + names = resolve_names(self._store.keys(), key, what=self._what) + return ElementArray("", [self._store[n] for n in names]) + def add(self, m: T): """ Add an element to the holder's name-indexed store. diff --git a/pyaml/common/holders/rf_holder.py b/pyaml/common/holders/rf_holder.py index 257c396a..0a2d1ed1 100644 --- a/pyaml/common/holders/rf_holder.py +++ b/pyaml/common/holders/rf_holder.py @@ -2,10 +2,12 @@ from typing import TYPE_CHECKING +from ...arrays.element_array import ElementArray from ...rf.rf_plant import RFPlant from ...rf.rf_transmitter import RFTransmitter from ..abstract import ReadWriteFloatScalar from ..element import __pyaml_repr__ +from ..name_matching import is_wildcard, resolve_names if TYPE_CHECKING: from .element_holder import ElementHolder @@ -50,6 +52,37 @@ def get(self, name: str) -> RFTransmitter: """ return self._peer._get("RFTransmitter", name, self._peer._RFTRANSMITTER) + def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "RFTransmitter | ElementArray": + """ + Return a transmitter, or a selection typed as a generic ElementArray. + + Parameters + ---------- + key : str, list[str] or tuple[str, ...] + An exact literal name returns the stored transmitter. An fnmatch + wildcard, a ``re:``-prefixed regular expression, or a list/tuple + of such patterns returns an ElementArray of matches (possibly + empty). + + Returns + ------- + RFTransmitter or ElementArray + The stored transmitter for an exact literal name, otherwise an + ElementArray of matches. + + Raises + ------ + PyAMLException + If an exact literal name, or a literal entry within a list or + tuple, does not match any transmitter, or a ``re:`` pattern is + not a valid regular expression. + """ + store = self._peer._RFTRANSMITTER + if isinstance(key, str) and not key.startswith("re:") and not is_wildcard(key): + return self._peer._get("RFTransmitter", key, store) + names = resolve_names(store.keys(), key, what="RFTransmitter") + return ElementArray("", [store[n] for n in names]) + def add(self, rf: RFTransmitter): """ Add an RF transmitter to the holder. @@ -134,6 +167,37 @@ def get(self, name: str) -> RFPlant: """ return self._peer._get("RFPlant", name, self._peer._RFPLANT) + def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "RFPlant | ElementArray": + """ + Return an RF plant, or a selection typed as a generic ElementArray. + + Parameters + ---------- + key : str, list[str] or tuple[str, ...] + An exact literal name returns the stored RF plant. An fnmatch + wildcard, a ``re:``-prefixed regular expression, or a list/tuple + of such patterns returns an ElementArray of matches (possibly + empty). + + Returns + ------- + RFPlant or ElementArray + The stored RF plant for an exact literal name, otherwise an + ElementArray of matches. + + Raises + ------ + PyAMLException + If an exact literal name, or a literal entry within a list or + tuple, does not match any RF plant, or a ``re:`` pattern is not + a valid regular expression. + """ + store = self._peer._RFPLANT + if isinstance(key, str) and not key.startswith("re:") and not is_wildcard(key): + return self._peer._get("RFPlant", key, store) + names = resolve_names(store.keys(), key, what="RFPlant") + return ElementArray("", [store[n] for n in names]) + def add(self, rf: RFPlant): """ Add an RF plant to the holder. diff --git a/pyaml/common/holders/tool_holder.py b/pyaml/common/holders/tool_holder.py index be7d17a0..4f8a8451 100644 --- a/pyaml/common/holders/tool_holder.py +++ b/pyaml/common/holders/tool_holder.py @@ -5,6 +5,7 @@ from ...arrays.element_array import ElementArray from ..element import Element, __pyaml_repr__ from ..exception import PyAMLException +from ..name_matching import is_wildcard, resolve_names if TYPE_CHECKING: from ...tuning_tools.chromaticity import Chromaticity @@ -86,6 +87,37 @@ def get(self, name: str = None) -> "Element | ElementArray": return ElementArray("", list(self._peer._TOOLS.values())) return self._peer._get_tool(name) + def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "Element | ElementArray": + """ + Return a tool, or a selection typed as a generic ElementArray. + + Parameters + ---------- + key : str, list[str] or tuple[str, ...] + An exact literal name returns the stored tool. An fnmatch + wildcard, a ``re:``-prefixed regular expression, or a list/tuple + of such patterns returns an ElementArray of matches (possibly + empty). + + Returns + ------- + Element or ElementArray + The stored tool for an exact literal name, otherwise an + ElementArray of matches. + + Raises + ------ + PyAMLException + If an exact literal name, or a literal entry within a list or + tuple, does not match any tool, or a ``re:`` pattern is not a + valid regular expression. + """ + store = self._peer._TOOLS + if isinstance(key, str) and not key.startswith("re:") and not is_wildcard(key): + return self._peer._get_tool(key) + names = resolve_names(store.keys(), key, what="Tool") + return ElementArray("", [store[n] for n in names]) + def _validate_type(self, name: str, obj: Element, expected_type: type) -> Element: """ Ensure a resolved default tool matches the type its accessor expects. diff --git a/tests/arrays/test_array_selection_types.py b/tests/arrays/test_array_selection_types.py index d5cc782b..11e65181 100644 --- a/tests/arrays/test_array_selection_types.py +++ b/tests/arrays/test_array_selection_types.py @@ -2,6 +2,7 @@ from pyaml.arrays.element_array import ElementArray from pyaml.arrays.magnet_array import MagnetArray +from pyaml.common.exception import PyAMLException @pytest.fixture @@ -60,3 +61,41 @@ def test_empty_selections_and_integer_indexing_keep_their_behavior(design): assert magnets["UNKNOWN*"] == [] assert magnets[0] is design.magnet.get(magnets.names()[0]) assert type(magnets - magnets) is list + + +def test_literal_name_miss_raises(design): + magnets = design.magnets.get() + + with pytest.raises(PyAMLException): + magnets["UNKNOWN"] + with pytest.raises(PyAMLException): + design.magnets["UNKNOWN"] + + +def test_literal_name_hit_still_returns_a_single_element_array(design): + magnets = design.magnets.get() + + selected = magnets["SH1A-C01-H"] + assert type(selected) is MagnetArray + assert selected.names() == ["SH1A-C01-H"] + + +def test_bracket_only_pattern_is_a_wildcard(design): + magnets = design.magnets.get() + assert sorted(magnets["SH1A-C0[12]-H"].names()) == ["SH1A-C01-H", "SH1A-C02-H"] + + +def test_regex_pattern_is_supported(design): + magnets = design.magnets.get() + assert sorted(magnets["re:^SH1A-C0[12]-H$"].names()) == ["SH1A-C01-H", "SH1A-C02-H"] + + +def test_list_of_patterns_is_supported(design): + magnets = design.magnets.get() + selected = magnets[["SH1A-C01-H", "SH1A-C02-H"]] + assert selected.names() == ["SH1A-C01-H", "SH1A-C02-H"] + + +def test_wildcard_matching_is_case_sensitive(design): + magnets = design.magnets.get() + assert magnets["sh1a*"] == [] diff --git a/tests/common/test_array_holder_navigation.py b/tests/common/test_array_holder_navigation.py index 7e630268..b1beea4e 100644 --- a/tests/common/test_array_holder_navigation.py +++ b/tests/common/test_array_holder_navigation.py @@ -2,6 +2,7 @@ from pyaml.accelerator import Accelerator from pyaml.arrays.cfm_magnet_array import CombinedFunctionMagnetArray +from pyaml.common.exception import PyAMLException @pytest.fixture @@ -97,3 +98,59 @@ def test_issue_373_example(): one_magnet = sr.design.magnet.get("QF1E-C04") one_magnet.strength.set(0.8) assert one_magnet.strength.get() == pytest.approx(0.8) + + +def test_configured_exclusion_family_reproduced_with_selection_and_difference(): + """QForTest is QForTune minus a `~name`/`~pattern` YAML exclusion; `[]` and `-` reproduce it.""" + sr = Accelerator.load("tests/config/EBSTune-patterns.yaml", ignore_external=True) + sr.design.get_lattice().disable_6d() + + q_for_tune = sr.design.magnets.get("QForTune") + q_for_test = sr.design.magnets.get("QForTest") # QForTune, minus ~QF1E-C05 and ~Q???-C06 + + reproduced = q_for_tune - sr.design.magnets["QF1E-C05"] - sr.design.magnets["Q???-C06"] + + assert reproduced == q_for_test + + +def test_magnet_holder_getitem_exact_name_matches_get(holder): + assert holder.magnet["SH1A-C01-H"] is holder.magnet.get("SH1A-C01-H") + + +def test_magnet_holder_getitem_exact_name_miss_raises(holder): + with pytest.raises(PyAMLException): + holder.magnet["UNKNOWN"] + + +def test_magnet_holder_getitem_wildcard_and_list_return_an_array(holder): + wildcard = holder.magnet["SH1A-C01-[HV]"] + assert sorted(wildcard.names()) == ["SH1A-C01-H", "SH1A-C01-V"] + assert holder.magnet["MISSING*"].names() == [] + + listed = holder.magnet[["SH1A-C01-H", "SH1A-C01-V"]] + assert listed.names() == ["SH1A-C01-H", "SH1A-C01-V"] + + +def test_magnet_holder_getitem_regex(holder): + matching = holder.magnet["re:^SH1A-C0[12]-H$"] + assert sorted(matching.names()) == ["SH1A-C01-H", "SH1A-C02-H"] + + +def test_combined_function_magnet_holder_getitem_exact_name_matches_get(holder): + assert holder.combined_function_magnet["SH1A-C01"] is holder.combined_function_magnet.get("SH1A-C01") + + +def test_combined_function_magnet_holder_getitem_exact_name_miss_raises(holder): + with pytest.raises(PyAMLException): + holder.combined_function_magnet["UNKNOWN"] + + +def test_serialized_magnet_holder_getitem_exact_name_matches_get(serialized_holder): + assert serialized_holder.serialized_magnet["mySeriesOfMagnets"] is serialized_holder.serialized_magnet.get( + "mySeriesOfMagnets" + ) + + +def test_serialized_magnet_holder_getitem_exact_name_miss_raises(serialized_holder): + with pytest.raises(PyAMLException): + serialized_holder.serialized_magnet["UNKNOWN"] diff --git a/tests/common/test_element_holder_collection.py b/tests/common/test_element_holder_collection.py index 4a55fadf..dd03865d 100644 --- a/tests/common/test_element_holder_collection.py +++ b/tests/common/test_element_holder_collection.py @@ -15,9 +15,10 @@ def holder(accelerator_from_fragments, sr_configuration_fragments): return sr.design -def test_exact_name_returns_the_element_or_none(holder): +def test_exact_name_returns_the_element_or_raises(holder): assert holder["BPM_C04-01"] is holder.diagnostic.bpm.get("BPM_C04-01") - assert holder["UNKNOWN"] is None + with pytest.raises(PyAMLException): + holder["UNKNOWN"] def test_get_and_full_slice_keep_registration_order(holder): @@ -113,7 +114,8 @@ def test_empty_holder_returns_empty_collections(ebs_lattice_file): assert type(holder.get()) is ElementArray assert holder[:].names() == [] assert holder["BPM*"].names() == [] - assert holder["BPM_C04-01"] is None + with pytest.raises(PyAMLException): + holder["BPM_C04-01"] def test_invalid_indices_and_keys_raise_clear_errors(holder): @@ -148,3 +150,107 @@ def test_existing_array_field_filters_still_work(holder): def test_existing_empty_intersection_stays_a_list(holder): assert type(holder["SH1A-C0?-H"] & holder["SH1A-C0?-V"]) is list + + +def test_intersecting_two_wildcard_selections(holder): + """Two overlapping wildcard selections combined with `&` narrow down to their overlap.""" + by_cell = holder["SH1A-C01*"] # SH1A-C01-H, SH1A-C01-V, SH1A-C01-SQ + by_plane = holder["SH1A-C0?-H"] # SH1A-C01-H, SH1A-C02-H + + selected = by_cell & by_plane + + assert isinstance(selected, MagnetArray) + assert selected.names() == ["SH1A-C01-H"] + + +def test_union_of_a_list_selection_and_a_wildcard_selection(holder): + """A list-of-patterns selection and a wildcard selection combine with `|` like any array.""" + cell_01 = holder[["SH1A-C01-H", "SH1A-C01-V"]] + cell_02 = holder["SH1A-C02*"] # the CFM magnet SH1A-C02, plus its -H, -V, -SQ sub-magnets + + combined = cell_01 | cell_02 + + # Mixes a CombinedFunctionMagnet (SH1A-C02) with plain Magnets, so the union + # stays a generic ElementArray rather than a MagnetArray. + assert type(combined) is ElementArray + assert sorted(combined.names()) == [ + "SH1A-C01-H", + "SH1A-C01-V", + "SH1A-C02", + "SH1A-C02-H", + "SH1A-C02-SQ", + "SH1A-C02-V", + ] + + +def test_difference_with_a_regex_selection(holder): + """`-` removes a `re:` selection's matches from a wildcard selection, like any array.""" + both_planes_h = holder["SH1A-C0?-H"] # SH1A-C01-H, SH1A-C02-H + cell_01_h = holder["re:^SH1A-C01-H$"] + + selected = both_planes_h - cell_01_h + + assert isinstance(selected, MagnetArray) + assert selected.names() == ["SH1A-C02-H"] + + +def test_disjoint_regex_and_list_selections_produce_a_plain_list(holder): + """The same empty-result-is-a-plain-list quirk applies to the new selection styles too.""" + cell_01_h = holder["re:^SH1A-C01-H$"] + cell_02_h_v = holder[["SH1A-C02-H", "SH1A-C02-V"]] + + assert type(cell_01_h & cell_02_h_v) is list + + +def test_selection_combined_with_a_configured_family_via_regex_and_list(holder): + """`re:` and list-of-patterns selections intersect with a configured family, same as wildcards.""" + el_array = holder.get_elements("ElArray") # BPM_C04-01, BPM_C04-02, SH1A-C01-V, SH1A-C02-H + + by_regex = holder["re:^SH1A-C0[12]-H$"] & el_array + assert isinstance(by_regex, MagnetArray) + assert by_regex.names() == ["SH1A-C02-H"] + + by_list = holder[["BPM_C04-01", "BPM_C04-02"]] & el_array + assert isinstance(by_list, BPMArray) + assert by_list.names() == ["BPM_C04-01", "BPM_C04-02"] + + +def test_regex_pattern_is_supported(holder): + assert holder["re:^BPM_C04-0[12]$"].names() == ["BPM_C04-01", "BPM_C04-02"] + assert holder["re:^NOTHING$"].names() == [] + + +def test_invalid_regex_raises_pyaml_exception(holder): + with pytest.raises(PyAMLException, match="Invalid regex"): + holder["re:("] + + +def test_list_of_patterns_unions_results(holder): + selected = holder[["BPM_C04-01", "SH1A-C0?-H"]] + assert selected.names() == ["BPM_C04-01", "SH1A-C01-H", "SH1A-C02-H"] + + +def test_list_of_patterns_with_missing_literal_raises(holder): + with pytest.raises(PyAMLException): + holder[["BPM_C04-01", "UNKNOWN"]] + + +def test_wildcard_matching_is_case_sensitive(holder): + assert holder["bpm_c04*"].names() == [] + + +def test_find_elements_literal_miss_raises(holder): + with pytest.raises(PyAMLException): + holder.find_elements("UNKNOWN") + + +def test_find_elements_bracket_only_pattern_is_a_wildcard(holder): + assert holder.find_elements("BPM_C04-0[12]") == ["BPM_C04-01", "BPM_C04-02"] + + +def test_find_elements_supports_regex(holder): + assert holder.find_elements("re:^BPM_C04-0[12]$") == ["BPM_C04-01", "BPM_C04-02"] + + +def test_find_elements_supports_a_list_of_patterns(holder): + assert holder.find_elements(["BPM_C04-01", "SH1A-C0?-H"]) == ["BPM_C04-01", "SH1A-C01-H", "SH1A-C02-H"] diff --git a/tests/common/test_name_matching.py b/tests/common/test_name_matching.py new file mode 100644 index 00000000..7e7319bf --- /dev/null +++ b/tests/common/test_name_matching.py @@ -0,0 +1,74 @@ +import pytest + +from pyaml.common.exception import PyAMLException +from pyaml.common.name_matching import is_wildcard, resolve_names + +NAMES = ["BPM_C04-01", "BPM_C04-02", "QF1A-C01", "QF1A-C02"] + + +def test_is_wildcard_triggers_on_star_question_and_bracket(): + assert is_wildcard("BPM*") + assert is_wildcard("BPM?") + assert is_wildcard("BPM[01]") + assert not is_wildcard("BPM_C04-01") + assert not is_wildcard("re:^BPM") + + +def test_literal_hit_returns_the_single_name(): + assert resolve_names(NAMES, "QF1A-C01") == ["QF1A-C01"] + + +def test_literal_miss_raises(): + with pytest.raises(PyAMLException, match="Element UNKNOWN not defined"): + resolve_names(NAMES, "UNKNOWN") + + +def test_literal_miss_uses_the_provided_what_label(): + with pytest.raises(PyAMLException, match="Magnet UNKNOWN not defined"): + resolve_names(NAMES, "UNKNOWN", what="Magnet") + + +def test_wildcard_hit_and_empty_are_not_errors(): + assert resolve_names(NAMES, "BPM*") == ["BPM_C04-01", "BPM_C04-02"] + assert resolve_names(NAMES, "MISSING*") == [] + + +def test_wildcard_is_case_sensitive(): + assert resolve_names(NAMES, "bpm*") == [] + + +def test_bracket_only_pattern_is_a_wildcard_not_a_literal(): + assert resolve_names(NAMES, "QF1A-C0[12]") == ["QF1A-C01", "QF1A-C02"] + + +def test_regex_hit_and_empty_are_not_errors(): + assert resolve_names(NAMES, "re:^BPM_C04-0[12]$") == ["BPM_C04-01", "BPM_C04-02"] + assert resolve_names(NAMES, "re:^NOTHING$") == [] + + +def test_regex_is_a_fullmatch(): + assert resolve_names(NAMES, "re:BPM_C04") == [] + + +def test_invalid_regex_raises_pyaml_exception_not_re_error(): + with pytest.raises(PyAMLException, match="Invalid regex"): + resolve_names(NAMES, "re:(") + + +def test_list_of_patterns_unions_and_deduplicates_in_order(): + result = resolve_names(NAMES, ["QF1A-C01", "BPM*", "QF1A-C01"]) + assert result == ["QF1A-C01", "BPM_C04-01", "BPM_C04-02"] + + +def test_tuple_of_patterns_is_also_accepted(): + assert resolve_names(NAMES, ("QF1A-C01", "QF1A-C02")) == ["QF1A-C01", "QF1A-C02"] + + +def test_list_with_one_missing_literal_raises(): + with pytest.raises(PyAMLException, match="Element UNKNOWN not defined"): + resolve_names(NAMES, ["QF1A-C01", "UNKNOWN"]) + + +def test_list_mixing_literal_wildcard_and_regex(): + result = resolve_names(NAMES, ["QF1A-C01", "BPM*", "re:^QF1A-C02$"]) + assert result == ["QF1A-C01", "BPM_C04-01", "BPM_C04-02", "QF1A-C02"] diff --git a/tests/diagnostics/test_diagnostic_accessors.py b/tests/diagnostics/test_diagnostic_accessors.py index 2247db71..342c04dd 100644 --- a/tests/diagnostics/test_diagnostic_accessors.py +++ b/tests/diagnostics/test_diagnostic_accessors.py @@ -79,3 +79,71 @@ def test_diagnostic_bpms_returns_named_array(): bpms = design.diagnostic.bpms.get("BPM") assert design.diagnostic.bpm.get("BPM_C04-04") in bpms + + +def test_diagnostic_getitem_exact_name_matches_get(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + assert design.diagnostic["BETATRON_TUNE"] is design.diagnostic.get("BETATRON_TUNE") + + +def test_diagnostic_getitem_exact_name_miss_raises(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + with pytest.raises(PyAMLException): + design.diagnostic["UNKNOWN"] + + +def test_diagnostic_getitem_wildcard_returns_an_array(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + matching = design.diagnostic["BETATRON*"] + assert matching.names() == ["BETATRON_TUNE"] + assert design.diagnostic["MISSING*"].names() == [] + + +def test_diagnostic_bpm_getitem_exact_name_matches_get(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + assert design.diagnostic.bpm["BPM_C04-04"] is design.diagnostic.bpm.get("BPM_C04-04") + + +def test_diagnostic_bpm_getitem_exact_name_miss_raises(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + with pytest.raises(PyAMLException): + design.diagnostic.bpm["UNKNOWN"] + + +def test_diagnostic_bpm_getitem_wildcard_and_list_return_an_array(): + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + wildcard = design.diagnostic.bpm["BPM_C04-0[14]"] + assert sorted(wildcard.names()) == ["BPM_C04-01", "BPM_C04-04"] + + listed = design.diagnostic.bpm[["BPM_C04-01", "BPM_C04-04"]] + assert listed.names() == ["BPM_C04-01", "BPM_C04-04"] diff --git a/tests/rf/test_rf_holder.py b/tests/rf/test_rf_holder.py new file mode 100644 index 00000000..784577a8 --- /dev/null +++ b/tests/rf/test_rf_holder.py @@ -0,0 +1,48 @@ +import pytest + +from pyaml.accelerator import Accelerator +from pyaml.common.exception import PyAMLException + + +@pytest.fixture +def design(): + return Accelerator.load("tests/config/EBS_rf_multi.yaml", ignore_external=True).design + + +def test_rf_holder_getitem_exact_name_matches_get(design): + assert design.rf["DEFAULT_RF_PLANT"] is design.rf.get("DEFAULT_RF_PLANT") + + +def test_rf_holder_getitem_exact_name_miss_raises(design): + with pytest.raises(PyAMLException): + design.rf["UNKNOWN"] + + +def test_rf_holder_getitem_wildcard_returns_an_array(design): + matching = design.rf["DEFAULT_*"] + assert matching.names() == ["DEFAULT_RF_PLANT"] + assert design.rf["MISSING*"].names() == [] + + +def test_rf_transmitter_holder_getitem_exact_name_matches_get(design): + assert design.rf.transmitter["RFTRA1"] is design.rf.transmitter.get("RFTRA1") + + +def test_rf_transmitter_holder_getitem_exact_name_miss_raises(design): + with pytest.raises(PyAMLException): + design.rf.transmitter["UNKNOWN"] + + +def test_rf_transmitter_holder_getitem_wildcard_returns_an_array(design): + matching = design.rf.transmitter["RFTRA*"] + assert sorted(matching.names()) == ["RFTRA1", "RFTRA2", "RFTRA_HARMONIC"] + + +def test_rf_transmitter_holder_getitem_list_of_patterns(design): + selected = design.rf.transmitter[["RFTRA1", "RFTRA2"]] + assert selected.names() == ["RFTRA1", "RFTRA2"] + + +def test_rf_transmitter_holder_getitem_regex(design): + matching = design.rf.transmitter["re:^RFTRA[12]$"] + assert sorted(matching.names()) == ["RFTRA1", "RFTRA2"] diff --git a/tests/tuning_tools/test_tool_holder.py b/tests/tuning_tools/test_tool_holder.py new file mode 100644 index 00000000..3821fcf3 --- /dev/null +++ b/tests/tuning_tools/test_tool_holder.py @@ -0,0 +1,38 @@ +import pytest + +from pyaml.accelerator import Accelerator +from pyaml.common.exception import PyAMLException + + +@pytest.fixture +def design(): + return Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + + +def test_tool_holder_getitem_exact_name_matches_get(design): + assert design.tool["DEFAULT_TUNE_CORRECTION"] is design.tool.get("DEFAULT_TUNE_CORRECTION") + + +def test_tool_holder_getitem_exact_name_miss_raises(design): + with pytest.raises(PyAMLException): + design.tool["UNKNOWN"] + + +def test_tool_holder_getitem_wildcard_returns_an_array(design): + matching = design.tool["DEFAULT_TUNE*"] + assert sorted(matching.names()) == ["DEFAULT_TUNE_CORRECTION", "DEFAULT_TUNE_RESPONSE_MATRIX"] + assert design.tool["MISSING*"].names() == [] + + +def test_tool_holder_getitem_list_of_patterns(design): + selected = design.tool[["DEFAULT_TUNE_CORRECTION", "DEFAULT_ORBIT_CORRECTION"]] + assert selected.names() == ["DEFAULT_TUNE_CORRECTION", "DEFAULT_ORBIT_CORRECTION"] + + +def test_tool_holder_getitem_regex(design): + matching = design.tool["re:^DEFAULT_(TUNE|ORBIT)_CORRECTION$"] + assert sorted(matching.names()) == ["DEFAULT_ORBIT_CORRECTION", "DEFAULT_TUNE_CORRECTION"] From b458038a847763c2791fef4e98d7510e9a56d9f9 Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Fri, 18 Sep 2026 17:18:49 +0200 Subject: [PATCH 18/49] Unify name/wildcard/regex lookup and miss-behavior across holders --- pyaml/common/name_matching.py | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 pyaml/common/name_matching.py diff --git a/pyaml/common/name_matching.py b/pyaml/common/name_matching.py new file mode 100644 index 00000000..cc228346 --- /dev/null +++ b/pyaml/common/name_matching.py @@ -0,0 +1,67 @@ +"""Shared name and wildcard resolution convention used by every holder and array.""" + +import fnmatch +import re +from typing import Iterable + +from .exception import PyAMLException + +WILDCARD_CHARS = frozenset("*?[") + + +def is_wildcard(pattern: str) -> bool: + """Return True if `pattern` contains an fnmatch wildcard character.""" + return any(c in pattern for c in WILDCARD_CHARS) + + +def resolve_names(pool: Iterable[str], pattern: str | list[str] | tuple[str, ...], what: str = "Element") -> list[str]: + """ + Resolve one pattern, or several, against a pool of names. + + Parameters + ---------- + pool : Iterable[str] + Names to match against. + pattern : str, list[str] or tuple[str, ...] + A literal name, an fnmatch wildcard (triggered by ``*``, ``?`` or + ``[`` anywhere in the string), or a regular expression prefixed with + ``re:``. A sequence of patterns is resolved entry by entry and the + results are unioned, de-duplicated, in first-encounter order. + + Returns + ------- + list[str] + Matching names. + + Raises + ------ + PyAMLException + If a literal pattern does not match any name in `pool`, or if a + `re:`-prefixed pattern is not a valid regular expression. + """ + names = list(pool) + patterns = pattern if isinstance(pattern, (list, tuple)) else [pattern] + + resolved: list[str] = [] + seen: set[str] = set() + for p in patterns: + for name in _resolve_one(names, p, what): + if name not in seen: + seen.add(name) + resolved.append(name) + return resolved + + +def _resolve_one(names: list[str], pattern: str, what: str) -> list[str]: + if pattern.startswith("re:"): + source = pattern[3:] + try: + compiled = re.compile(source) + except re.error as exc: + raise PyAMLException(f"Invalid regex '{source}': {exc}") from exc + return [n for n in names if compiled.fullmatch(n)] + if is_wildcard(pattern): + return [n for n in names if fnmatch.fnmatchcase(n, pattern)] + if pattern not in names: + raise PyAMLException(f"{what} {pattern} not defined") + return [pattern] From b229b01caed726120a0ac6eecfec1b6c97504853 Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Mon, 21 Sep 2026 10:43:07 +0200 Subject: [PATCH 19/49] Support ~ exclusion patterns in the public list-of-patterns resolver --- pyaml/arrays/element_array.py | 5 ++- pyaml/common/holders/diagnostic_holder.py | 2 +- pyaml/common/holders/element_holder.py | 21 ++++++---- .../common/holders/generic_element_holder.py | 2 +- pyaml/common/holders/rf_holder.py | 4 +- pyaml/common/holders/tool_holder.py | 2 +- pyaml/common/name_matching.py | 31 ++++++++++---- tests/common/test_array_holder_navigation.py | 17 ++++++++ .../common/test_element_holder_collection.py | 21 ++++++++++ tests/common/test_name_matching.py | 40 +++++++++++++++++++ 10 files changed, 125 insertions(+), 20 deletions(-) diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index ffa9e3d9..428b8e1d 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -567,7 +567,9 @@ def __getitem__(self, key): literal name (must match an element in this array), an fnmatch wildcard (``*``, ``?`` or ``[``), or a ``re:``-prefixed regular expression. A list or tuple resolves each entry independently - and unions the results. + and unions the results. Prefix a name pattern with ``~`` to + exclude its matches instead; a lone ``~pattern`` means every + element except those matches. Returns ------- @@ -592,6 +594,7 @@ def __getitem__(self, key): >>> correctors = magnets["SH*"] # MagnetArray >>> correctors = magnets["re:^SH1A-C0[12]-H$"] # MagnetArray >>> selection = magnets[["SH1A-C01-H", "SH*-V"]] # Union of patterns + >>> all_but_one = magnets["~SH1A-C01-H"] # Every magnet except one """ if isinstance(key, slice): # Slicing diff --git a/pyaml/common/holders/diagnostic_holder.py b/pyaml/common/holders/diagnostic_holder.py index 66213bda..588b74c7 100644 --- a/pyaml/common/holders/diagnostic_holder.py +++ b/pyaml/common/holders/diagnostic_holder.py @@ -122,7 +122,7 @@ def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "Element | Elem not a valid regular expression. """ store = self._peer._DIAG - if isinstance(key, str) and not key.startswith("re:") and not is_wildcard(key): + if isinstance(key, str) and not key.startswith(("re:", "~")) and not is_wildcard(key): return self._peer._get_diagnostic(key) names = resolve_names(store.keys(), key, what="Diagnostic") return ElementArray("", [store[n] for n in names]) diff --git a/pyaml/common/holders/element_holder.py b/pyaml/common/holders/element_holder.py index 07751ee7..9cf8be5d 100644 --- a/pyaml/common/holders/element_holder.py +++ b/pyaml/common/holders/element_holder.py @@ -319,9 +319,11 @@ def find_elements(self, filter: str | list[str] | tuple[str, ...]) -> list[str]: filter : str, list[str] or tuple[str, ...] Pattern, or patterns, to match. A pattern is a literal name, an fnmatch wildcard (``*``, ``?`` or ``[`` anywhere in the string), - or a regular expression prefixed with ``re:``. Several patterns - are resolved independently and unioned, de-duplicated, in - first-encounter order. + or a regular expression prefixed with ``re:``. Prefix any of + those with ``~`` to exclude its matches instead, as in the + ``elements:`` selector list of a YAML array declaration, e.g. + ``["QD2*", "QF1*", "~QF1E-C05"]``. A lone ``~pattern`` (or a list + made only of ``~`` entries) means "every element except those". Returns ------- @@ -331,8 +333,8 @@ def find_elements(self, filter: str | list[str] | tuple[str, ...]) -> list[str]: Raises ------ PyAMLException - If a literal pattern matches no element, or a ``re:`` pattern is - not a valid regular expression. + If a literal pattern (or a ``~``-prefixed literal) matches no + element, or a ``re:`` pattern is not a valid regular expression. """ return resolve_names(self._ALL.keys(), filter, what="Element") @@ -467,7 +469,10 @@ def __getitem__(self, key: int | slice | str | list[str] | tuple[str, ...]) -> E ``[`` use fnmatch matching; a ``re:`` prefix uses a regular expression instead. Any other string is an exact registry key. Colons are literal. A list or tuple resolves each entry - independently and unions the results. + independently and unions the results. Prefix any pattern with + ``~`` to exclude its matches instead, as in a YAML array's + ``elements:`` list; a lone ``~pattern`` means every element + except those matches. Returns ------- @@ -508,12 +513,14 @@ def __getitem__(self, key: int | slice | str | list[str] | tuple[str, ...]) -> E >>> bpms = sr.live["BPM0[!3]"] # One character after BPM0, except 3 >>> bpms = sr.live["re:^BPM0[12]$"] # Regular expression >>> mixed = sr.live[["BPM01", "QF1*"]] # Union of several patterns + >>> all_but_one = sr.live["~BPM01"] # Every element except BPM01 + >>> most_quads = sr.live[["QF1*", "~QF1A-C01"]] # QF1* minus one name >>> first = sr.live[0] >>> subset = sr.live[1:10] >>> all_elements = sr.live[:] """ if isinstance(key, str): - if key.startswith("re:") or is_wildcard(key): + if key.startswith("re:") or key.startswith("~") or is_wildcard(key): names = resolve_names(self._ALL.keys(), key) return self.get()._typed_array([self._ALL[n] for n in names]) if key not in self._ALL: diff --git a/pyaml/common/holders/generic_element_holder.py b/pyaml/common/holders/generic_element_holder.py index ce5b85a4..101796b9 100644 --- a/pyaml/common/holders/generic_element_holder.py +++ b/pyaml/common/holders/generic_element_holder.py @@ -101,7 +101,7 @@ def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "T | ElementArr tuple, does not match any element in this holder, or a ``re:`` pattern is not a valid regular expression. """ - if isinstance(key, str) and not key.startswith("re:") and not is_wildcard(key): + if isinstance(key, str) and not key.startswith(("re:", "~")) and not is_wildcard(key): return self._peer._get(self._what, key, self._store) names = resolve_names(self._store.keys(), key, what=self._what) return ElementArray("", [self._store[n] for n in names]) diff --git a/pyaml/common/holders/rf_holder.py b/pyaml/common/holders/rf_holder.py index 0a2d1ed1..8b81d7c9 100644 --- a/pyaml/common/holders/rf_holder.py +++ b/pyaml/common/holders/rf_holder.py @@ -78,7 +78,7 @@ def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "RFTransmitter not a valid regular expression. """ store = self._peer._RFTRANSMITTER - if isinstance(key, str) and not key.startswith("re:") and not is_wildcard(key): + if isinstance(key, str) and not key.startswith(("re:", "~")) and not is_wildcard(key): return self._peer._get("RFTransmitter", key, store) names = resolve_names(store.keys(), key, what="RFTransmitter") return ElementArray("", [store[n] for n in names]) @@ -193,7 +193,7 @@ def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "RFPlant | Elem a valid regular expression. """ store = self._peer._RFPLANT - if isinstance(key, str) and not key.startswith("re:") and not is_wildcard(key): + if isinstance(key, str) and not key.startswith(("re:", "~")) and not is_wildcard(key): return self._peer._get("RFPlant", key, store) names = resolve_names(store.keys(), key, what="RFPlant") return ElementArray("", [store[n] for n in names]) diff --git a/pyaml/common/holders/tool_holder.py b/pyaml/common/holders/tool_holder.py index 4f8a8451..d02c044b 100644 --- a/pyaml/common/holders/tool_holder.py +++ b/pyaml/common/holders/tool_holder.py @@ -113,7 +113,7 @@ def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "Element | Elem valid regular expression. """ store = self._peer._TOOLS - if isinstance(key, str) and not key.startswith("re:") and not is_wildcard(key): + if isinstance(key, str) and not key.startswith(("re:", "~")) and not is_wildcard(key): return self._peer._get_tool(key) names = resolve_names(store.keys(), key, what="Tool") return ElementArray("", [store[n] for n in names]) diff --git a/pyaml/common/name_matching.py b/pyaml/common/name_matching.py index cc228346..9125cd3a 100644 --- a/pyaml/common/name_matching.py +++ b/pyaml/common/name_matching.py @@ -25,8 +25,16 @@ def resolve_names(pool: Iterable[str], pattern: str | list[str] | tuple[str, ... pattern : str, list[str] or tuple[str, ...] A literal name, an fnmatch wildcard (triggered by ``*``, ``?`` or ``[`` anywhere in the string), or a regular expression prefixed with - ``re:``. A sequence of patterns is resolved entry by entry and the - results are unioned, de-duplicated, in first-encounter order. + ``re:``. Prefix any of those with ``~`` to exclude its matches + instead of including them, mirroring the ``elements:`` selector list + convention used in YAML configuration. A sequence of patterns is + resolved entry by entry: non-``~`` entries are unioned, + de-duplicated, in first-encounter order, then anything matched by a + ``~`` entry is removed from that union. If every entry is + ``~``-prefixed (including a single lone ``~pattern``, treated as a + one-entry sequence), there is no explicit inclusion, so the base set + defaults to the full pool: ``~pattern`` alone means "everything + except pattern". Returns ------- @@ -36,20 +44,29 @@ def resolve_names(pool: Iterable[str], pattern: str | list[str] | tuple[str, ... Raises ------ PyAMLException - If a literal pattern does not match any name in `pool`, or if a - `re:`-prefixed pattern is not a valid regular expression. + If a literal pattern (or the remainder of a ``~``-prefixed one) does + not match any name in `pool`, or if a `re:`-prefixed pattern is not + a valid regular expression. """ names = list(pool) patterns = pattern if isinstance(pattern, (list, tuple)) else [pattern] - resolved: list[str] = [] + included: list[str] = [] seen: set[str] = set() + excluded: set[str] = set() + has_inclusion = False for p in patterns: + if p.startswith("~"): + excluded.update(_resolve_one(names, p[1:], what)) + continue + has_inclusion = True for name in _resolve_one(names, p, what): if name not in seen: seen.add(name) - resolved.append(name) - return resolved + included.append(name) + + base = included if has_inclusion else names + return [n for n in base if n not in excluded] def _resolve_one(names: list[str], pattern: str, what: str) -> list[str]: diff --git a/tests/common/test_array_holder_navigation.py b/tests/common/test_array_holder_navigation.py index b1beea4e..04cc2480 100644 --- a/tests/common/test_array_holder_navigation.py +++ b/tests/common/test_array_holder_navigation.py @@ -113,6 +113,23 @@ def test_configured_exclusion_family_reproduced_with_selection_and_difference(): assert reproduced == q_for_test +def test_configured_exclusion_family_reproduced_in_a_single_getitem_call(): + """The YAML `elements:` selector list (`[QD2*, QF1*, ~QF1E-C05, ~Q???-C06]`) works verbatim through `[]`. + + Selection goes through the top-level holder, not `.magnets`, so it resolves patterns + against the same global element pool `find_elements()`/`_fill_array` use to build + `QForTest` in the YAML, and so lands in the same order. + """ + sr = Accelerator.load("tests/config/EBSTune-patterns.yaml", ignore_external=True) + sr.design.get_lattice().disable_6d() + + q_for_test = sr.design.magnets.get("QForTest") + reproduced = sr.design[["QD2*", "QF1*", "~QF1E-C05", "~Q???-C06"]] + + assert type(reproduced) is type(q_for_test) + assert reproduced == q_for_test + + def test_magnet_holder_getitem_exact_name_matches_get(holder): assert holder.magnet["SH1A-C01-H"] is holder.magnet.get("SH1A-C01-H") diff --git a/tests/common/test_element_holder_collection.py b/tests/common/test_element_holder_collection.py index dd03865d..c6c889d5 100644 --- a/tests/common/test_element_holder_collection.py +++ b/tests/common/test_element_holder_collection.py @@ -254,3 +254,24 @@ def test_find_elements_supports_regex(holder): def test_find_elements_supports_a_list_of_patterns(holder): assert holder.find_elements(["BPM_C04-01", "SH1A-C0?-H"]) == ["BPM_C04-01", "SH1A-C01-H", "SH1A-C02-H"] + + +def test_find_elements_supports_an_exclusion_in_a_list(holder): + both_planes = holder.find_elements(["SH1A-C0?-H", "SH1A-C0?-V"]) + assert both_planes == ["SH1A-C01-H", "SH1A-C02-H", "SH1A-C01-V", "SH1A-C02-V"] + + minus_one = holder.find_elements(["SH1A-C0?-H", "SH1A-C0?-V", "~SH1A-C02-V"]) + assert minus_one == ["SH1A-C01-H", "SH1A-C02-H", "SH1A-C01-V"] + + +def test_getitem_supports_an_exclusion_in_a_list(holder): + selected = holder[["SH1A-C0?-H", "SH1A-C0?-V", "~SH1A-C02-V"]] + assert selected.names() == ["SH1A-C01-H", "SH1A-C02-H", "SH1A-C01-V"] + + +def test_getitem_lone_exclusion_pattern_means_everything_except(holder): + all_names = holder.get_all_elements() + selected = holder["~QF1A-C01"] + + assert "QF1A-C01" not in selected.names() + assert len(selected) == len(all_names) - 1 diff --git a/tests/common/test_name_matching.py b/tests/common/test_name_matching.py index 7e7319bf..c7365862 100644 --- a/tests/common/test_name_matching.py +++ b/tests/common/test_name_matching.py @@ -72,3 +72,43 @@ def test_list_with_one_missing_literal_raises(): def test_list_mixing_literal_wildcard_and_regex(): result = resolve_names(NAMES, ["QF1A-C01", "BPM*", "re:^QF1A-C02$"]) assert result == ["QF1A-C01", "BPM_C04-01", "BPM_C04-02", "QF1A-C02"] + + +def test_overlapping_wildcard_patterns_in_a_list_do_not_duplicate_a_match(): + names = ["BPM_01", "BPM_02", "BPM_03", "BPM_04", "BPM_05"] + result = resolve_names(names, ["BPM_0[1-3]", "BPM_0[3-5]"]) + assert result == ["BPM_01", "BPM_02", "BPM_03", "BPM_04", "BPM_05"] + + +def test_lone_exclusion_pattern_means_everything_except(): + assert resolve_names(NAMES, "~QF1A-C01") == ["BPM_C04-01", "BPM_C04-02", "QF1A-C02"] + + +def test_list_of_only_exclusion_patterns_means_everything_except(): + result = resolve_names(NAMES, ["~QF1A-C01", "~QF1A-C02"]) + assert result == ["BPM_C04-01", "BPM_C04-02"] + + +def test_exclusion_removes_matches_from_an_inclusion_pattern(): + assert resolve_names(NAMES, ["BPM*", "~BPM_C04-01"]) == ["BPM_C04-02"] + + +def test_exclusion_order_does_not_matter(): + forward = resolve_names(NAMES, ["BPM*", "~BPM_C04-01"]) + backward = resolve_names(NAMES, ["~BPM_C04-01", "BPM*"]) + assert forward == backward == ["BPM_C04-02"] + + +def test_exclusion_of_a_wildcard_pattern_removes_all_its_matches(): + result = resolve_names(NAMES, ["QF1A-C01", "QF1A-C02", "BPM_C04-01", "~QF1A-*"]) + assert result == ["BPM_C04-01"] + + +def test_exclusion_of_a_regex_pattern_removes_all_its_matches(): + result = resolve_names(NAMES, ["BPM*", "~re:^BPM_C04-01$"]) + assert result == ["BPM_C04-02"] + + +def test_exclusion_of_a_missing_literal_raises(): + with pytest.raises(PyAMLException, match="Element UNKNOWN not defined"): + resolve_names(NAMES, ["BPM*", "~UNKNOWN"]) From 4f4c0170879ce3f6fc76580d6481c6fe4fe72f2a Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Thu, 24 Sep 2026 11:09:20 +0200 Subject: [PATCH 20/49] Correction of pattern parsing --- pyaml/arrays/element_array.py | 2 +- pyaml/common/holders/element_holder.py | 13 +++----- tests/common/test_array_holder_navigation.py | 34 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index 428b8e1d..f0522113 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -606,7 +606,7 @@ def __getitem__(self, key): r = [e for e in self if e.get_name() in matched] elif isinstance(key, str): - fields = [] if key.startswith("re:") else key.split(":") + fields = [] if key.lstrip("~").startswith("re:") else key.split(":") if len(fields) <= 1: # Selection by name pattern diff --git a/pyaml/common/holders/element_holder.py b/pyaml/common/holders/element_holder.py index 9cf8be5d..046db388 100644 --- a/pyaml/common/holders/element_holder.py +++ b/pyaml/common/holders/element_holder.py @@ -346,7 +346,6 @@ def _fill_array( constructor, ARR: dict, ): - # Handle wildcard, regexp and exclusion pattern """ Resolve selectors and store a constructed element array. @@ -364,16 +363,14 @@ def _fill_array( Destination array store. """ all_names: list[str] = [] - excluded_names: list[str] = [] + excluded_names: set[str] = set() for name in element_names: if name.startswith("~"): - names = self.find_elements(name[1:]) - excluded_names.extend(names) + excluded_names.update(self.find_elements(name[1:])) else: - names = self.find_elements(name) - all_names.extend(names) + all_names.extend(self.find_elements(name)) - [all_names.remove(name) for name in excluded_names] + all_names = [n for n in all_names if n not in excluded_names] a = [] for n in all_names: @@ -382,7 +379,7 @@ def _fill_array( except Exception as err: raise PyAMLException(f"{constructor.__name__} {array_name} : {err} @index {len(a)}") from None if m in a: - raise PyAMLException(f"{constructor.__name__} {array_name} : duplicate name {name} @index {len(a)}") from None + raise PyAMLException(f"{constructor.__name__} {array_name} : duplicate name {n} @index {len(a)}") from None a.append(m) ARR[array_name] = constructor(array_name, a) diff --git a/tests/common/test_array_holder_navigation.py b/tests/common/test_array_holder_navigation.py index 04cc2480..056df3b1 100644 --- a/tests/common/test_array_holder_navigation.py +++ b/tests/common/test_array_holder_navigation.py @@ -3,6 +3,7 @@ from pyaml.accelerator import Accelerator from pyaml.arrays.cfm_magnet_array import CombinedFunctionMagnetArray from pyaml.common.exception import PyAMLException +from pyaml.configuration.manager import ConfigurationManager @pytest.fixture @@ -130,6 +131,39 @@ def test_configured_exclusion_family_reproduced_in_a_single_getitem_call(): assert reproduced == q_for_test +def test_array_getitem_supports_a_negated_regex_pattern(): + """`~re:...` on an ElementArray excludes regex matches, same as `~wildcard`.""" + sr = Accelerator.load("tests/config/EBSTune-patterns.yaml", ignore_external=True) + sr.design.get_lattice().disable_6d() + + magnets = sr.design.magnets[:] + wildcard_exclusion = magnets["~QF1*"] + regex_exclusion = magnets["~re:^QF1.*$"] + + assert len(regex_exclusion) > 0 + assert len(regex_exclusion) < len(magnets) + assert regex_exclusion == wildcard_exclusion + + +def test_fill_array_supports_an_exclusion_outside_a_single_getitem_call_shape(): + """A YAML `elements:` list combining an inclusion and an exclusion builds an array, not a ValueError. + + `_fill_array` previously did its own include/exclude bookkeeping with `list.remove`, + which raised whenever the excluded name wasn't already gathered by an inclusion + pattern processed earlier in the same call. Delegating to `find_elements` (used here + with the same list, for comparison) avoids that ordering trap. + """ + manager = ConfigurationManager() + manager.add("tests/config/EBSTune-patterns.yaml") + manager.add({"arrays": [{"type": "pyaml.arrays.magnet", "name": "QD2Family", "elements": ["QD2*", "~QF1E-C05"]}]}) + sr = manager.build(ignore_external=True) + sr.design.get_lattice().disable_6d() + + qd2_family = sr.design.magnets.get("QD2Family") + + assert qd2_family.names() == sr.design.find_elements(["QD2*", "~QF1E-C05"]) + + def test_magnet_holder_getitem_exact_name_matches_get(holder): assert holder.magnet["SH1A-C01-H"] is holder.magnet.get("SH1A-C01-H") From ca492f72e864478b5e41e8a7e22a93970c97e85f Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Thu, 24 Sep 2026 15:29:05 +0200 Subject: [PATCH 21/49] Correction of pattern parsing for order and auto typing --- pyaml/common/holders/generic_element_holder.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pyaml/common/holders/generic_element_holder.py b/pyaml/common/holders/generic_element_holder.py index 101796b9..d5c404ff 100644 --- a/pyaml/common/holders/generic_element_holder.py +++ b/pyaml/common/holders/generic_element_holder.py @@ -77,22 +77,22 @@ def get(self, name: str) -> T: def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "T | ElementArray": """ - Return an element, or a selection typed as a generic ElementArray. + Return an element, or a selection typed as the most specific compatible array. Parameters ---------- key : str, list[str] or tuple[str, ...] An exact literal name returns the stored element. An fnmatch wildcard (``*``, ``?`` or ``[``), a ``re:``-prefixed regular - expression, or a list/tuple of such patterns returns an - :class:`~pyaml.arrays.element_array.ElementArray` of matches - (possibly empty). + expression, or a list/tuple of such patterns returns the most + specific compatible array of matches (possibly empty), like + :meth:`.ElementHolder.__getitem__`. Returns ------- T or ElementArray - The stored element for an exact literal name, otherwise an - ElementArray of matches. + The stored element for an exact literal name, otherwise a + typed array of matches. Raises ------ @@ -103,8 +103,9 @@ def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "T | ElementArr """ if isinstance(key, str) and not key.startswith(("re:", "~")) and not is_wildcard(key): return self._peer._get(self._what, key, self._store) - names = resolve_names(self._store.keys(), key, what=self._what) - return ElementArray("", [self._store[n] for n in names]) + matched = set(resolve_names(self._store.keys(), key, what=self._what)) + elements = [v for n, v in self._store.items() if n in matched] + return ElementArray("", elements)._typed_array(elements) def add(self, m: T): """ From f576343a05d3cd594c4b247c6af47f40e0d00242 Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Thu, 24 Sep 2026 15:47:59 +0200 Subject: [PATCH 22/49] Fix aggregator crash when a set operation auto-types to a CFM or serialized-magnet array --- pyaml/arrays/element_array.py | 8 +++-- tests/common/test_array_holder_navigation.py | 35 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index f0522113..26c7b65b 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -137,13 +137,17 @@ def __create_array(self, array_name: str, element_type: type, elements: list): array_class = getattr(m, "BPMArray", None) return array_class(array_name, elements, self.__use_aggregator) elif issubclass(element_type, CombinedFunctionMagnet): + # CombinedFunctionMagnetArray has no aggregator support, unlike the + # array type this selection was derived from. m = importlib.import_module("pyaml.arrays.cfm_magnet_array") array_class = getattr(m, "CombinedFunctionMagnetArray", None) - return array_class(array_name, elements, self.__use_aggregator) + return array_class(array_name, elements, False) elif issubclass(element_type, SerializedMagnets): + # SerializedMagnetsArray has no aggregator support, unlike the + # array type this selection was derived from. m = importlib.import_module("pyaml.arrays.serialized_magnet_array") array_class = getattr(m, "SerializedMagnetsArray", None) - return array_class(array_name, elements, self.__use_aggregator) + return array_class(array_name, elements, False) elif issubclass(element_type, Element): return ElementArray(array_name, elements, self.__use_aggregator) else: diff --git a/tests/common/test_array_holder_navigation.py b/tests/common/test_array_holder_navigation.py index 056df3b1..d76331bd 100644 --- a/tests/common/test_array_holder_navigation.py +++ b/tests/common/test_array_holder_navigation.py @@ -2,6 +2,7 @@ from pyaml.accelerator import Accelerator from pyaml.arrays.cfm_magnet_array import CombinedFunctionMagnetArray +from pyaml.arrays.magnet_array import MagnetArray from pyaml.common.exception import PyAMLException from pyaml.configuration.manager import ConfigurationManager @@ -182,6 +183,40 @@ def test_magnet_holder_getitem_wildcard_and_list_return_an_array(holder): assert listed.names() == ["SH1A-C01-H", "SH1A-C01-V"] +def test_magnet_holder_getitem_wildcard_returns_a_magnet_array(holder): + """A wildcard restricted to the .magnet store is auto-typed as MagnetArray, like the + top-level and .magnets holders, instead of falling back to a generic ElementArray.""" + wildcard = holder.magnet["SH1A-C0[12]-[HVQ]*"] + assert type(wildcard) is MagnetArray + + +def test_magnet_and_magnets_holder_getitem_agree_on_order(holder): + """.magnet (singular) and .magnets (plural) must return the same elements in the same + order for the same name list, regardless of the order the names were requested in. The + reference order is the configuration order, not the order of the requested names.""" + forward = holder.magnet[["SH1A-C01-H", "SH1A-C01-V"]] + backward = holder.magnet[["SH1A-C01-V", "SH1A-C01-H"]] + assert forward.names() == backward.names() == ["SH1A-C01-H", "SH1A-C01-V"] + + forward_plural = holder.magnets[["SH1A-C01-H", "SH1A-C01-V"]] + backward_plural = holder.magnets[["SH1A-C01-V", "SH1A-C01-H"]] + assert forward_plural.names() == backward_plural.names() == forward.names() + + +def test_subtracting_a_magnet_selection_from_a_mixed_selection_yields_a_cfm_array(holder): + """A top-level selection mixing CombinedFunctionMagnet and their virtual correctors, minus + the corrector-only .magnets selection, must yield the CFMs alone as a + CombinedFunctionMagnetArray. Regression test: this used to raise 'Aggregator not + implemented for CombinedFunctionMagnetArray' because the derived array blindly inherited + the use_aggregator flag of the ElementArray it was built from.""" + mixed = holder["SH*"] + only_magnets = holder.magnets["SH*"] + only_cfm = mixed - only_magnets + + assert type(only_cfm) is CombinedFunctionMagnetArray + assert only_cfm.names() == ["SH1A-C01", "SH1A-C02"] + + def test_magnet_holder_getitem_regex(holder): matching = holder.magnet["re:^SH1A-C0[12]-H$"] assert sorted(matching.names()) == ["SH1A-C01-H", "SH1A-C02-H"] From 279fea065db2ed2072eaf88c284513eb1778452a Mon Sep 17 00:00:00 2001 From: Alexis Gamelin Date: Thu, 24 Sep 2026 17:01:14 +0200 Subject: [PATCH 23/49] Add a test for array order --- tests/common/test_array_holder_navigation.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/common/test_array_holder_navigation.py b/tests/common/test_array_holder_navigation.py index d76331bd..e28ca14d 100644 --- a/tests/common/test_array_holder_navigation.py +++ b/tests/common/test_array_holder_navigation.py @@ -203,6 +203,26 @@ def test_magnet_and_magnets_holder_getitem_agree_on_order(holder): assert forward_plural.names() == backward_plural.names() == forward.names() +@pytest.mark.parametrize( + "select", + [ + pytest.param(lambda h, k: h[k].names(), id="holder[]"), + pytest.param(lambda h, k: h.find_elements(k), id="holder.find_elements"), + pytest.param(lambda h, k: h.get()[k].names(), id="holder.get()[]"), + pytest.param(lambda h, k: h.magnet[k].names(), id="holder.magnet[]"), + pytest.param(lambda h, k: h.magnets[k].names(), id="holder.magnets[]"), + ], +) +@pytest.mark.parametrize("reverse", [False, True], ids=["forward", "reversed"]) +def test_every_accessor_returns_a_list_selection_in_configuration_order(holder, select, reverse): + """Every name-based accessor must return a list-of-patterns selection in configuration + order, whatever the order of the requested names, so that positional values line up the + same way whichever accessor built the array.""" + key = ["SH1A-C01-H", "SH1A-C01-V", "SH1A-C02-H"] + expected = [n for n in holder.get().names() if n in key] + assert select(holder, key[::-1] if reverse else key) == expected + + def test_subtracting_a_magnet_selection_from_a_mixed_selection_yields_a_cfm_array(holder): """A top-level selection mixing CombinedFunctionMagnet and their virtual correctors, minus the corrector-only .magnets selection, must yield the CFMs alone as a From db35db7e2718161e09610f4afe9e500c2faa2a89 Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Mon, 28 Sep 2026 11:42:19 +0200 Subject: [PATCH 24/49] Normalize list-selection order to configuration order across every holder --- pyaml/common/holders/diagnostic_holder.py | 4 +- pyaml/common/holders/element_holder.py | 11 +-- pyaml/common/holders/rf_holder.py | 8 +-- pyaml/common/holders/tool_holder.py | 4 +- tests/common/test_array_holder_navigation.py | 71 ++++++++++++++++--- .../common/test_element_holder_collection.py | 12 ++-- .../diagnostics/test_diagnostic_accessors.py | 23 ++++++ tests/rf/test_rf_holder.py | 27 +++++++ tests/tuning_tools/test_tool_holder.py | 9 +++ 9 files changed, 142 insertions(+), 27 deletions(-) diff --git a/pyaml/common/holders/diagnostic_holder.py b/pyaml/common/holders/diagnostic_holder.py index 588b74c7..b78eefbf 100644 --- a/pyaml/common/holders/diagnostic_holder.py +++ b/pyaml/common/holders/diagnostic_holder.py @@ -124,8 +124,8 @@ def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "Element | Elem store = self._peer._DIAG if isinstance(key, str) and not key.startswith(("re:", "~")) and not is_wildcard(key): return self._peer._get_diagnostic(key) - names = resolve_names(store.keys(), key, what="Diagnostic") - return ElementArray("", [store[n] for n in names]) + matched = set(resolve_names(store.keys(), key, what="Diagnostic")) + return ElementArray("", [v for n, v in store.items() if n in matched]) @property def betatron_tune(self) -> BetatronTuneMonitor: diff --git a/pyaml/common/holders/element_holder.py b/pyaml/common/holders/element_holder.py index 046db388..5eed50c3 100644 --- a/pyaml/common/holders/element_holder.py +++ b/pyaml/common/holders/element_holder.py @@ -336,7 +336,8 @@ def find_elements(self, filter: str | list[str] | tuple[str, ...]) -> list[str]: If a literal pattern (or a ``~``-prefixed literal) matches no element, or a ``re:`` pattern is not a valid regular expression. """ - return resolve_names(self._ALL.keys(), filter, what="Element") + matched = set(resolve_names(self._ALL.keys(), filter, what="Element")) + return [n for n in self._ALL if n in matched] def _fill_array( self, @@ -518,14 +519,14 @@ def __getitem__(self, key: int | slice | str | list[str] | tuple[str, ...]) -> E """ if isinstance(key, str): if key.startswith("re:") or key.startswith("~") or is_wildcard(key): - names = resolve_names(self._ALL.keys(), key) - return self.get()._typed_array([self._ALL[n] for n in names]) + matched = set(resolve_names(self._ALL.keys(), key)) + return self.get()._typed_array([v for n, v in self._ALL.items() if n in matched]) if key not in self._ALL: raise PyAMLException(f"Element {key} not defined") return self._ALL[key] if isinstance(key, (list, tuple)): - names = resolve_names(self._ALL.keys(), key) - return self.get()._typed_array([self._ALL[n] for n in names]) + matched = set(resolve_names(self._ALL.keys(), key)) + return self.get()._typed_array([v for n, v in self._ALL.items() if n in matched]) if isinstance(key, int): return list(self._ALL.values())[key] if isinstance(key, slice): diff --git a/pyaml/common/holders/rf_holder.py b/pyaml/common/holders/rf_holder.py index 8b81d7c9..43c07028 100644 --- a/pyaml/common/holders/rf_holder.py +++ b/pyaml/common/holders/rf_holder.py @@ -80,8 +80,8 @@ def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "RFTransmitter store = self._peer._RFTRANSMITTER if isinstance(key, str) and not key.startswith(("re:", "~")) and not is_wildcard(key): return self._peer._get("RFTransmitter", key, store) - names = resolve_names(store.keys(), key, what="RFTransmitter") - return ElementArray("", [store[n] for n in names]) + matched = set(resolve_names(store.keys(), key, what="RFTransmitter")) + return ElementArray("", [v for n, v in store.items() if n in matched]) def add(self, rf: RFTransmitter): """ @@ -195,8 +195,8 @@ def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "RFPlant | Elem store = self._peer._RFPLANT if isinstance(key, str) and not key.startswith(("re:", "~")) and not is_wildcard(key): return self._peer._get("RFPlant", key, store) - names = resolve_names(store.keys(), key, what="RFPlant") - return ElementArray("", [store[n] for n in names]) + matched = set(resolve_names(store.keys(), key, what="RFPlant")) + return ElementArray("", [v for n, v in store.items() if n in matched]) def add(self, rf: RFPlant): """ diff --git a/pyaml/common/holders/tool_holder.py b/pyaml/common/holders/tool_holder.py index d02c044b..2ac49e86 100644 --- a/pyaml/common/holders/tool_holder.py +++ b/pyaml/common/holders/tool_holder.py @@ -115,8 +115,8 @@ def __getitem__(self, key: str | list[str] | tuple[str, ...]) -> "Element | Elem store = self._peer._TOOLS if isinstance(key, str) and not key.startswith(("re:", "~")) and not is_wildcard(key): return self._peer._get_tool(key) - names = resolve_names(store.keys(), key, what="Tool") - return ElementArray("", [store[n] for n in names]) + matched = set(resolve_names(store.keys(), key, what="Tool")) + return ElementArray("", [v for n, v in store.items() if n in matched]) def _validate_type(self, name: str, obj: Element, expected_type: type) -> Element: """ diff --git a/tests/common/test_array_holder_navigation.py b/tests/common/test_array_holder_navigation.py index e28ca14d..8bd0d2a9 100644 --- a/tests/common/test_array_holder_navigation.py +++ b/tests/common/test_array_holder_navigation.py @@ -116,11 +116,17 @@ def test_configured_exclusion_family_reproduced_with_selection_and_difference(): def test_configured_exclusion_family_reproduced_in_a_single_getitem_call(): - """The YAML `elements:` selector list (`[QD2*, QF1*, ~QF1E-C05, ~Q???-C06]`) works verbatim through `[]`. + """The YAML `elements:` selector list (`[QD2*, QF1*, ~QF1E-C05, ~Q???-C06]`) selects the + same *family* of elements through `[]` as declared for `QForTest`. Selection goes through the top-level holder, not `.magnets`, so it resolves patterns against the same global element pool `find_elements()`/`_fill_array` use to build - `QForTest` in the YAML, and so lands in the same order. + `QForTest` in the YAML. The two therefore agree on *which* elements are selected, but not + necessarily on their order: `_fill_array` concatenates matches pattern by pattern (all + `QD2*` matches, then all `QF1*` matches) to build the named array, while `[]` (like every + other list-selection accessor, see `test_every_accessor_returns_a_list_selection_in_configuration_order`) + normalizes its result to configuration order regardless of how many patterns were + involved or in what order they matched. """ sr = Accelerator.load("tests/config/EBSTune-patterns.yaml", ignore_external=True) sr.design.get_lattice().disable_6d() @@ -129,7 +135,8 @@ def test_configured_exclusion_family_reproduced_in_a_single_getitem_call(): reproduced = sr.design[["QD2*", "QF1*", "~QF1E-C05", "~Q???-C06"]] assert type(reproduced) is type(q_for_test) - assert reproduced == q_for_test + assert set(reproduced.names()) == set(q_for_test.names()) + assert reproduced.names() == [n for n in sr.design.get().names() if n in set(q_for_test.names())] def test_array_getitem_supports_a_negated_regex_pattern(): @@ -193,7 +200,9 @@ def test_magnet_holder_getitem_wildcard_returns_a_magnet_array(holder): def test_magnet_and_magnets_holder_getitem_agree_on_order(holder): """.magnet (singular) and .magnets (plural) must return the same elements in the same order for the same name list, regardless of the order the names were requested in. The - reference order is the configuration order, not the order of the requested names.""" + reference order is the configuration order, not the order of the requested names. This + must also hold for the top-level holder itself (`[]` and `find_elements`), not just its + typed sub-holders.""" forward = holder.magnet[["SH1A-C01-H", "SH1A-C01-V"]] backward = holder.magnet[["SH1A-C01-V", "SH1A-C01-H"]] assert forward.names() == backward.names() == ["SH1A-C01-H", "SH1A-C01-V"] @@ -202,6 +211,33 @@ def test_magnet_and_magnets_holder_getitem_agree_on_order(holder): backward_plural = holder.magnets[["SH1A-C01-V", "SH1A-C01-H"]] assert forward_plural.names() == backward_plural.names() == forward.names() + forward_top_level = holder[["SH1A-C01-H", "SH1A-C01-V"]] + backward_top_level = holder[["SH1A-C01-V", "SH1A-C01-H"]] + assert forward_top_level.names() == backward_top_level.names() == forward.names() + + assert holder.find_elements(["SH1A-C01-H", "SH1A-C01-V"]) == forward.names() + assert holder.find_elements(["SH1A-C01-V", "SH1A-C01-H"]) == forward.names() + + +def test_combined_function_magnet_and_bpm_holders_return_list_selections_in_configuration_order(holder): + """Every typed sub-holder must normalize list selections to configuration order, not just + .magnet/.magnets: this must also hold for .combined_function_magnet(s) and .bpm(s).""" + cfm_forward = holder.combined_function_magnet[["SH1A-C01", "SH1A-C02"]] + cfm_backward = holder.combined_function_magnet[["SH1A-C02", "SH1A-C01"]] + assert cfm_forward.names() == cfm_backward.names() == ["SH1A-C01", "SH1A-C02"] + + cfm_forward_plural = holder.combined_function_magnets[["SH1A-C01", "SH1A-C02"]] + cfm_backward_plural = holder.combined_function_magnets[["SH1A-C02", "SH1A-C01"]] + assert cfm_forward_plural.names() == cfm_backward_plural.names() == cfm_forward.names() + + bpm_forward = holder.diagnostic.bpm[["BPM_C04-01", "BPM_C04-02"]] + bpm_backward = holder.diagnostic.bpm[["BPM_C04-02", "BPM_C04-01"]] + assert bpm_forward.names() == bpm_backward.names() == ["BPM_C04-01", "BPM_C04-02"] + + bpm_forward_plural = holder.diagnostic.bpms[["BPM_C04-01", "BPM_C04-02"]] + bpm_backward_plural = holder.diagnostic.bpms[["BPM_C04-02", "BPM_C04-01"]] + assert bpm_forward_plural.names() == bpm_backward_plural.names() == bpm_forward.names() + @pytest.mark.parametrize( "select", @@ -214,12 +250,29 @@ def test_magnet_and_magnets_holder_getitem_agree_on_order(holder): ], ) @pytest.mark.parametrize("reverse", [False, True], ids=["forward", "reversed"]) -def test_every_accessor_returns_a_list_selection_in_configuration_order(holder, select, reverse): +@pytest.mark.parametrize( + "key, expected_members", + [ + pytest.param( + ["SH1A-C01-H", "SH1A-C01-V", "SH1A-C02-H"], + {"SH1A-C01-H", "SH1A-C01-V", "SH1A-C02-H"}, + id="literal-names", + ), + pytest.param( + ["SH1A-C0?-H", "SH1A-C01-V"], + {"SH1A-C01-H", "SH1A-C02-H", "SH1A-C01-V"}, + id="wildcard-and-literal", + ), + ], +) +def test_every_accessor_returns_a_list_selection_in_configuration_order(holder, select, reverse, key, expected_members): """Every name-based accessor must return a list-of-patterns selection in configuration - order, whatever the order of the requested names, so that positional values line up the - same way whichever accessor built the array.""" - key = ["SH1A-C01-H", "SH1A-C01-V", "SH1A-C02-H"] - expected = [n for n in holder.get().names() if n in key] + order, whatever the order of the requested names/patterns, so that positional values line + up the same way whichever accessor built the array. This must hold both for a list of + literal names and for a list mixing a wildcard (matching several names) with a literal + name, since a wildcard entry is where a naive pattern-order passthrough diverges from + configuration order the most visibly.""" + expected = [n for n in holder.get().names() if n in expected_members] assert select(holder, key[::-1] if reverse else key) == expected diff --git a/tests/common/test_element_holder_collection.py b/tests/common/test_element_holder_collection.py index c6c889d5..3fdfc88d 100644 --- a/tests/common/test_element_holder_collection.py +++ b/tests/common/test_element_holder_collection.py @@ -226,8 +226,9 @@ def test_invalid_regex_raises_pyaml_exception(holder): def test_list_of_patterns_unions_results(holder): + """Order follows configuration (registration) order, not the order patterns were requested in.""" selected = holder[["BPM_C04-01", "SH1A-C0?-H"]] - assert selected.names() == ["BPM_C04-01", "SH1A-C01-H", "SH1A-C02-H"] + assert selected.names() == ["SH1A-C01-H", "SH1A-C02-H", "BPM_C04-01"] def test_list_of_patterns_with_missing_literal_raises(holder): @@ -253,20 +254,21 @@ def test_find_elements_supports_regex(holder): def test_find_elements_supports_a_list_of_patterns(holder): - assert holder.find_elements(["BPM_C04-01", "SH1A-C0?-H"]) == ["BPM_C04-01", "SH1A-C01-H", "SH1A-C02-H"] + """Order follows configuration (registration) order, not the order patterns were requested in.""" + assert holder.find_elements(["BPM_C04-01", "SH1A-C0?-H"]) == ["SH1A-C01-H", "SH1A-C02-H", "BPM_C04-01"] def test_find_elements_supports_an_exclusion_in_a_list(holder): both_planes = holder.find_elements(["SH1A-C0?-H", "SH1A-C0?-V"]) - assert both_planes == ["SH1A-C01-H", "SH1A-C02-H", "SH1A-C01-V", "SH1A-C02-V"] + assert both_planes == ["SH1A-C01-H", "SH1A-C01-V", "SH1A-C02-H", "SH1A-C02-V"] minus_one = holder.find_elements(["SH1A-C0?-H", "SH1A-C0?-V", "~SH1A-C02-V"]) - assert minus_one == ["SH1A-C01-H", "SH1A-C02-H", "SH1A-C01-V"] + assert minus_one == ["SH1A-C01-H", "SH1A-C01-V", "SH1A-C02-H"] def test_getitem_supports_an_exclusion_in_a_list(holder): selected = holder[["SH1A-C0?-H", "SH1A-C0?-V", "~SH1A-C02-V"]] - assert selected.names() == ["SH1A-C01-H", "SH1A-C02-H", "SH1A-C01-V"] + assert selected.names() == ["SH1A-C01-H", "SH1A-C01-V", "SH1A-C02-H"] def test_getitem_lone_exclusion_pattern_means_everything_except(holder): diff --git a/tests/diagnostics/test_diagnostic_accessors.py b/tests/diagnostics/test_diagnostic_accessors.py index 342c04dd..2acdb9c3 100644 --- a/tests/diagnostics/test_diagnostic_accessors.py +++ b/tests/diagnostics/test_diagnostic_accessors.py @@ -1,3 +1,5 @@ +import copy + import pytest from pyaml.accelerator import Accelerator @@ -114,6 +116,27 @@ def test_diagnostic_getitem_wildcard_returns_an_array(): assert design.diagnostic["MISSING*"].names() == [] +def test_diagnostic_getitem_list_selection_ignores_request_order(): + """A list-of-patterns selection lands in configuration order, whatever order the names + were requested in, same as every other holder (see + test_every_accessor_returns_a_list_selection_in_configuration_order). Only one diagnostic + is registered by any existing fixture, so a distinct second entry is poked directly into + the store (a renamed copy of the existing monitor) to make request order and + configuration order observably different.""" + design = Accelerator.load( + "tests/config/EBSOrbit.yaml", + ignore_external=True, + include_locations=False, + ).design + spare_monitor = copy.copy(design.get_betatron_tune_monitor("BETATRON_TUNE")) + spare_monitor._name = "SPARE_BETATRON_TUNE" + design._DIAG["SPARE_BETATRON_TUNE"] = spare_monitor + + forward = design.diagnostic[["BETATRON_TUNE", "SPARE_BETATRON_TUNE"]] + backward = design.diagnostic[["SPARE_BETATRON_TUNE", "BETATRON_TUNE"]] + assert forward.names() == backward.names() == ["BETATRON_TUNE", "SPARE_BETATRON_TUNE"] + + def test_diagnostic_bpm_getitem_exact_name_matches_get(): design = Accelerator.load( "tests/config/EBSOrbit.yaml", diff --git a/tests/rf/test_rf_holder.py b/tests/rf/test_rf_holder.py index 784577a8..b1839ea8 100644 --- a/tests/rf/test_rf_holder.py +++ b/tests/rf/test_rf_holder.py @@ -1,3 +1,5 @@ +import copy + import pytest from pyaml.accelerator import Accelerator @@ -43,6 +45,31 @@ def test_rf_transmitter_holder_getitem_list_of_patterns(design): assert selected.names() == ["RFTRA1", "RFTRA2"] +def test_rf_transmitter_holder_getitem_list_selection_ignores_request_order(design): + """A list-of-patterns selection lands in configuration order, whatever order the names + were requested in, same as every other holder (see + test_every_accessor_returns_a_list_selection_in_configuration_order).""" + forward = design.rf.transmitter[["RFTRA1", "RFTRA2"]] + backward = design.rf.transmitter[["RFTRA2", "RFTRA1"]] + assert forward.names() == backward.names() == ["RFTRA1", "RFTRA2"] + + +def test_rf_holder_getitem_list_selection_ignores_request_order(design): + """Same configuration-order guarantee as the RF transmitter holder, at the RF plant level. + + Only one RF plant is registered by this fixture, so a distinct second entry is poked + directly into the store (a renamed copy of the existing plant) to make request order and + configuration order observably different. + """ + spare_plant = copy.copy(design.rf.get("DEFAULT_RF_PLANT")) + spare_plant._name = "SPARE_RF_PLANT" + design._RFPLANT["SPARE_RF_PLANT"] = spare_plant + + forward = design.rf[["DEFAULT_RF_PLANT", "SPARE_RF_PLANT"]] + backward = design.rf[["SPARE_RF_PLANT", "DEFAULT_RF_PLANT"]] + assert forward.names() == backward.names() == ["DEFAULT_RF_PLANT", "SPARE_RF_PLANT"] + + def test_rf_transmitter_holder_getitem_regex(design): matching = design.rf.transmitter["re:^RFTRA[12]$"] assert sorted(matching.names()) == ["RFTRA1", "RFTRA2"] diff --git a/tests/tuning_tools/test_tool_holder.py b/tests/tuning_tools/test_tool_holder.py index 3821fcf3..cd921940 100644 --- a/tests/tuning_tools/test_tool_holder.py +++ b/tests/tuning_tools/test_tool_holder.py @@ -33,6 +33,15 @@ def test_tool_holder_getitem_list_of_patterns(design): assert selected.names() == ["DEFAULT_TUNE_CORRECTION", "DEFAULT_ORBIT_CORRECTION"] +def test_tool_holder_getitem_list_selection_ignores_request_order(design): + """A list-of-patterns selection lands in configuration order, whatever order the names + were requested in, same as every other holder (see + test_every_accessor_returns_a_list_selection_in_configuration_order).""" + forward = design.tool[["DEFAULT_TUNE_CORRECTION", "DEFAULT_ORBIT_CORRECTION"]] + backward = design.tool[["DEFAULT_ORBIT_CORRECTION", "DEFAULT_TUNE_CORRECTION"]] + assert forward.names() == backward.names() == ["DEFAULT_TUNE_CORRECTION", "DEFAULT_ORBIT_CORRECTION"] + + def test_tool_holder_getitem_regex(design): matching = design.tool["re:^DEFAULT_(TUNE|ORBIT)_CORRECTION$"] assert sorted(matching.names()) == ["DEFAULT_ORBIT_CORRECTION", "DEFAULT_TUNE_CORRECTION"] From e1fa68795f956ede7bbd52b369e466780d30ef62 Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Mon, 28 Sep 2026 16:53:23 +0200 Subject: [PATCH 25/49] Remove ElementHolder backward-compatible tool aliases per review --- .../other_examples/ORM_Tango_server/ORM.py | 4 +- examples/use_cases/01-tune_correction.ipynb | 207 +----------------- examples/use_cases/01-tune_correction.py | 28 +-- examples/use_cases/03-orbit_correction.ipynb | 190 +--------------- examples/use_cases/03-orbit_correction.py | 32 +-- examples/use_cases/README.md | 10 +- pyaml/common/holders/element_holder.py | 40 ---- pyaml/common/holders/tool_holder.py | 20 +- .../chromaticity_response_matrix.py | 8 +- pyaml/tuning_tools/measurement_tool.py | 2 +- pyaml/tuning_tools/orbit_response_matrix.py | 8 +- pyaml/tuning_tools/tune_response_matrix.py | 8 +- .../diagnostics/test_diagnostic_accessors.py | 2 +- tests/tuning_tools/test_tool_accessors.py | 74 +++---- tests/tuning_tools/test_tuning_dispersion.py | 2 +- .../test_tuning_orbit_correction.py | 58 ++--- tests/tuning_tools/test_tuning_orm.py | 4 +- tests/tuning_tools/test_tuning_tools.py | 24 +- 18 files changed, 152 insertions(+), 569 deletions(-) diff --git a/examples/other_examples/ORM_Tango_server/ORM.py b/examples/other_examples/ORM_Tango_server/ORM.py index 186476af..fe686076 100644 --- a/examples/other_examples/ORM_Tango_server/ORM.py +++ b/examples/other_examples/ORM_Tango_server/ORM.py @@ -102,8 +102,8 @@ def orbit_callback(self, action: int, cdata): def orm_run(self): # On design sleep 10ms to allow thread schedule - self.SR.design.orm.measure(callback=orbit_callback, sleep_between_meas=0.01) - self.orm_data = self.SR.design.orm.get() + self.SR.design.tool.orm.measure(callback=orbit_callback, sleep_between_meas=0.01) + self.orm_data = self.SR.design.tool.orm.get() self.set_status(f"Ready to scan: {self.ConfigFileName}") self.set_state(DevState.ON) diff --git a/examples/use_cases/01-tune_correction.ipynb b/examples/use_cases/01-tune_correction.ipynb index dfba75bb..cf6e3471 100644 --- a/examples/use_cases/01-tune_correction.ipynb +++ b/examples/use_cases/01-tune_correction.ipynb @@ -282,83 +282,15 @@ "cell_type": "markdown", "id": "04431246", "metadata": {}, - "source": [ - "### Standard tune correction tool\n", - "\n", - "`SR.tune` is the `DEFAULT_TUNE_CORRECTION` tool. `SR.trm` is the `DEFAULT_TUNE_RESPONSE_MATRIX` tool.\n", - "\n", - "Before correcting the tune you need a response matrix. It can be measured (below) or loaded from a previously saved file." - ] + "source": "### Standard tune correction tool\n\n`SR.tool.tune` is the `DEFAULT_TUNE_CORRECTION` tool. `SR.tool.trm` is the `DEFAULT_TUNE_RESPONSE_MATRIX` tool.\n\nBefore correcting the tune you need a response matrix. It can be measured (below) or loaded from a previously saved file." }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "5c57ef30", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Tune(quad_array_name='QForTune', betatron_tune_name='BETATRON_TUNE', response_matrix=array([[ 0.17853128, 0.17890919, 0.17917984, 0.17843061, 0.17932054,\n", - " 0.17884878, 0.1786898 , 0.17950221, 0.17836418, 0.1790288 ,\n", - " 0.17907229, 0.17845344, 0.17936885, 0.1787317 , 0.17880339,\n", - " 0.1793422 , 0.17843576, 0.17914062, 0.17895506, 0.17850485,\n", - " 0.15843663, 0.1575313 , 0.15237977, 0.15250702, 0.17830408,\n", - " 0.17955648, 0.1784584 , 0.17880469, 0.17936621, 0.17824945,\n", - " 0.17941811, 0.17905372, 0.17841113, 0.17957284, 0.17835113,\n", - " 0.17897118, 0.17923226, 0.17825399, 0.17950825, 0.17876573,\n", - " 0.17866201, 0.17954684, 0.17827911, 0.17925585, 0.17897151,\n", - " 0.17830041, 0.17956082, 0.17873061, 0.17871689, 0.17948152,\n", - " 0.15577439, 0.15662709, 0.178933 , 0.17847488, 0.17937198,\n", - " 0.17881763, 0.17874021, 0.17931638, 0.17843723, 0.17929549,\n", - " 0.17880609, 0.17853484, 0.59082023, 0.59180433, 0.59274339,\n", - " 0.59079241, 0.59387998, 0.59265135, 0.59209988, 0.59391444,\n", - " 0.59072413, 0.59219559, 0.59234655, 0.59073497, 0.59391106,\n", - " 0.59225191, 0.59250056, 0.59390362, 0.59075881, 0.59259427,\n", - " 0.5919504 , 0.59077803, 0.52411485, 0.52163044, 0.50248335,\n", - " 0.50266086, 0.59115919, 0.59419444, 0.59038465, 0.59128648,\n", - " 0.59323464, 0.59032653, 0.59438116, 0.59313174, 0.59165988,\n", - " 0.5944257 , 0.59018699, 0.59180163, 0.59270748, 0.59015999,\n", - " 0.59451179, 0.5925782 , 0.59221837, 0.59452251, 0.59012406,\n", - " 0.59236712, 0.59213945, 0.59013148, 0.59450445, 0.59204248,\n", - " 0.59275305, 0.59448392, 0.51467256, 0.51681741, 0.59219055,\n", - " 0.59076114, 0.59387372, 0.59213029, 0.59261978, 0.59385979,\n", - " 0.59080955, 0.59274399, 0.59180476, 0.59083819],\n", - " [-1.26055967, -1.26031469, -1.2598023 , -1.26083158, -1.25981216,\n", - " -1.26160634, -1.26143231, -1.25920552, -1.26001143, -1.26105243,\n", - " -1.26026455, -1.26115601, -1.26145837, -1.25972946, -1.26035568,\n", - " -1.26035017, -1.25972691, -1.26145864, -1.2611518 , -1.26026992,\n", - " -1.28718859, -1.28580227, -1.29551207, -1.29591315, -1.26023455,\n", - " -1.26119118, -1.2611013 , -1.26019174, -1.26077014, -1.26007772,\n", - " -1.25987122, -1.260857 , -1.2609502 , -1.26075456, -1.26134921,\n", - " -1.25965397, -1.25996982, -1.26071455, -1.25976978, -1.26108183,\n", - " -1.26034272, -1.26107775, -1.26118967, -1.25922595, -1.25959951,\n", - " -1.26157551, -1.26072113, -1.26058879, -1.26193197, -1.25939288,\n", - " -1.28878699, -1.28913722, -1.26030408, -1.26116608, -1.26092241,\n", - " -1.25960761, -1.2599948 , -1.26128422, -1.26055622, -1.26012439,\n", - " -1.26052166, -1.2609009 , -0.49429912, -0.49420118, -0.49402884,\n", - " -0.49444032, -0.49392715, -0.49464442, -0.49464248, -0.49384803,\n", - " -0.4940031 , -0.49450349, -0.49410766, -0.49446404, -0.49461625,\n", - " -0.49392508, -0.49424032, -0.49423811, -0.49392437, -0.49461667,\n", - " -0.4944622 , -0.49410966, -0.49580432, -0.4952599 , -0.49584758,\n", - " -0.49606291, -0.49405329, -0.49450455, -0.4944593 , -0.49409569,\n", - " -0.49438685, -0.49411004, -0.49400608, -0.49449611, -0.4942921 ,\n", - " -0.49429813, -0.49459747, -0.49391976, -0.49407876, -0.49437648,\n", - " -0.49398646, -0.49451098, -0.49413892, -0.49443276, -0.49457543,\n", - " -0.49388618, -0.49382348, -0.49469773, -0.49426765, -0.49431067,\n", - " -0.49473566, -0.49380506, -0.49553592, -0.49574219, -0.49407224,\n", - " -0.4944902 , -0.4944531 , -0.49402325, -0.49396739, -0.49456722,\n", - " -0.49428634, -0.49420954, -0.49415863, -0.49439461]]), _tm=BetatronTuneMonitor(tune_h='srdiag/beam-tune/main/Qh', tune_v='srdiag/beam-tune/main/Qv', name='BETATRON_TUNE'), _quads=[Quadrupole(peer='Simulator:design', name='QD2E-C04', model_name='QD2E-C04', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C05', model_name='QD2A-C05', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C05', model_name='QD2E-C05', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C06', model_name='QD2A-C06', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C06', model_name='QD2E-C06', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C07', model_name='QD2A-C07', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C07', model_name='QD2E-C07', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C08', model_name='QD2A-C08', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C08', model_name='QD2E-C08', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C09', model_name='QD2A-C09', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C09', model_name='QD2E-C09', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C10', model_name='QD2A-C10', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C10', model_name='QD2E-C10', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C11', model_name='QD2A-C11', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C11', model_name='QD2E-C11', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C12', model_name='QD2A-C12', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C12', model_name='QD2E-C12', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C13', model_name='QD2A-C13', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C13', model_name='QD2E-C13', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C14', model_name='QD2A-C14', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C14', model_name='QD2E-C14', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C15', model_name='QD2A-C15', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C15', model_name='QD2E-C15', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C16', model_name='QD2A-C16', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C16', model_name='QD2E-C16', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C17', model_name='QD2A-C17', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C17', model_name='QD2E-C17', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C18', model_name='QD2A-C18', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C18', model_name='QD2E-C18', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C19', model_name='QD2A-C19', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C19', model_name='QD2E-C19', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C20', model_name='QD2A-C20', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C20', model_name='QD2E-C20', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C21', model_name='QD2A-C21', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C21', model_name='QD2E-C21', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C22', model_name='QD2A-C22', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C22', model_name='QD2E-C22', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C23', model_name='QD2A-C23', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C23', model_name='QD2E-C23', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C24', model_name='QD2A-C24', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C24', model_name='QD2E-C24', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C25', model_name='QD2A-C25', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C25', model_name='QD2E-C25', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C26', model_name='QD2A-C26', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C26', model_name='QD2E-C26', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C27', model_name='QD2A-C27', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C27', model_name='QD2E-C27', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C28', model_name='QD2A-C28', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C28', model_name='QD2E-C28', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C29', model_name='QD2A-C29', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C29', model_name='QD2E-C29', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C30', model_name='QD2A-C30', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C30', model_name='QD2E-C30', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C31', model_name='QD2A-C31', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C31', model_name='QD2E-C31', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C32', model_name='QD2A-C32', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C32', model_name='QD2E-C32', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C01', model_name='QD2A-C01', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C01', model_name='QD2E-C01', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C02', model_name='QD2A-C02', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C02', model_name='QD2E-C02', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C03', model_name='QD2A-C03', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C04', model_name='QF1E-C04', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C05', model_name='QF1A-C05', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C05', model_name='QF1E-C05', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C06', model_name='QF1A-C06', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C06', model_name='QF1E-C06', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C07', model_name='QF1A-C07', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C07', model_name='QF1E-C07', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C08', model_name='QF1A-C08', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C08', model_name='QF1E-C08', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C09', model_name='QF1A-C09', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C09', model_name='QF1E-C09', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C10', model_name='QF1A-C10', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C10', model_name='QF1E-C10', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C11', model_name='QF1A-C11', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C11', model_name='QF1E-C11', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C12', model_name='QF1A-C12', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C12', model_name='QF1E-C12', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C13', model_name='QF1A-C13', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C13', model_name='QF1E-C13', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C14', model_name='QF1A-C14', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C14', model_name='QF1E-C14', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C15', model_name='QF1A-C15', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C15', model_name='QF1E-C15', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C16', model_name='QF1A-C16', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C16', model_name='QF1E-C16', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C17', model_name='QF1A-C17', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C17', model_name='QF1E-C17', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C18', model_name='QF1A-C18', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C18', model_name='QF1E-C18', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C19', model_name='QF1A-C19', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C19', model_name='QF1E-C19', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C20', model_name='QF1A-C20', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C20', model_name='QF1E-C20', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C21', model_name='QF1A-C21', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C21', model_name='QF1E-C21', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C22', model_name='QF1A-C22', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C22', model_name='QF1E-C22', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C23', model_name='QF1A-C23', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C23', model_name='QF1E-C23', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C24', model_name='QF1A-C24', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C24', model_name='QF1E-C24', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C25', model_name='QF1A-C25', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C25', model_name='QF1E-C25', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C26', model_name='QF1A-C26', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C26', model_name='QF1E-C26', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C27', model_name='QF1A-C27', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C27', model_name='QF1E-C27', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C28', model_name='QF1A-C28', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C28', model_name='QF1E-C28', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C29', model_name='QF1A-C29', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C29', model_name='QF1E-C29', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C30', model_name='QF1A-C30', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C30', model_name='QF1E-C30', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C31', model_name='QF1A-C31', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C31', model_name='QF1E-C31', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C32', model_name='QF1A-C32', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C32', model_name='QF1E-C32', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C01', model_name='QF1A-C01', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C01', model_name='QF1E-C01', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C02', model_name='QF1A-C02', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C02', model_name='QF1E-C02', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C03', model_name='QF1A-C03', magnet_model=LinearMagnetModel)], name='DEFAULT_TUNE_CORRECTION')" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "SR.tune # string representation" - ] + "outputs": [], + "source": "SR.tool.tune # string representation" }, { "cell_type": "markdown", @@ -391,142 +323,25 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "c81cdb05", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Response matrix loaded.\n" - ] - }, - { - "data": { - "text/plain": [ - "TuneResponseMatrix(quad_array_name='QForTune', betatron_tune_name='BETATRON_TUNE', quad_delta=0.0001, n_step=1, sleep_between_step=0.0, n_avg_meas=1, sleep_between_meas=0.0, name='DEFAULT_TUNE_RESPONSE_MATRIX')" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "if SR.tune.response_matrix is None:\n", - " SR.trm.measure(sleep_between_step=wait_time, callback=tune_callback)\n", - " SR.trm.save(CONFIG_DIR / \"trm.json\")\n", - "\n", - "SR.tune.load(CONFIG_DIR / \"trm.json\")\n", - "print(\"Response matrix loaded.\")\n", - "SR.trm # string representation" - ] + "outputs": [], + "source": "if SR.tool.tune.response_matrix is None:\n SR.tool.trm.measure(sleep_between_step=wait_time, callback=tune_callback)\n SR.tool.trm.save(CONFIG_DIR / \"trm.json\")\n\nSR.tool.tune.load(CONFIG_DIR / \"trm.json\")\nprint(\"Response matrix loaded.\")\nSR.tool.trm # string representation" }, { "cell_type": "markdown", "id": "ef7d7ce4", "metadata": {}, - "source": [ - "#### Correcting the tune\n", - "\n", - "`SR.tune.set([qx, qy])` runs the correction iteratively. The `iter` parameter controls the number of iterations and `wait_time` the settling time between each iteration." - ] + "source": "#### Correcting the tune\n\n`SR.tool.tune.set([qx, qy])` runs the correction iteratively. The `iter` parameter controls the number of iterations and `wait_time` the settling time between each iteration." }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "6b8bcf91", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Tune before correction: [0.16000001 0.33999986]\n", - "\n", - "Setting tune to [0.19, 0.28]\n", - "Tune after correction: [0.19 0.28]\n", - "\n", - "Setting tune to [0.21, 0.3]\n", - "Tune after correction: [0.21 0.3 ]\n" - ] - }, - { - "data": { - "text/plain": [ - "Tune(quad_array_name='QForTune', betatron_tune_name='BETATRON_TUNE', response_matrix=array([[ 0.17853128, 0.17890919, 0.17917984, 0.17843061, 0.17932054,\n", - " 0.17884878, 0.1786898 , 0.17950221, 0.17836418, 0.1790288 ,\n", - " 0.17907229, 0.17845344, 0.17936885, 0.1787317 , 0.17880339,\n", - " 0.1793422 , 0.17843576, 0.17914062, 0.17895506, 0.17850485,\n", - " 0.15843663, 0.1575313 , 0.15237977, 0.15250702, 0.17830408,\n", - " 0.17955648, 0.1784584 , 0.17880469, 0.17936621, 0.17824945,\n", - " 0.17941811, 0.17905372, 0.17841113, 0.17957284, 0.17835113,\n", - " 0.17897118, 0.17923226, 0.17825399, 0.17950825, 0.17876573,\n", - " 0.17866201, 0.17954684, 0.17827911, 0.17925585, 0.17897151,\n", - " 0.17830041, 0.17956082, 0.17873061, 0.17871689, 0.17948152,\n", - " 0.15577439, 0.15662709, 0.178933 , 0.17847488, 0.17937198,\n", - " 0.17881763, 0.17874021, 0.17931638, 0.17843723, 0.17929549,\n", - " 0.17880609, 0.17853484, 0.59082023, 0.59180433, 0.59274339,\n", - " 0.59079241, 0.59387998, 0.59265135, 0.59209988, 0.59391444,\n", - " 0.59072413, 0.59219559, 0.59234655, 0.59073497, 0.59391106,\n", - " 0.59225191, 0.59250056, 0.59390362, 0.59075881, 0.59259427,\n", - " 0.5919504 , 0.59077803, 0.52411485, 0.52163044, 0.50248335,\n", - " 0.50266086, 0.59115919, 0.59419444, 0.59038465, 0.59128648,\n", - " 0.59323464, 0.59032653, 0.59438116, 0.59313174, 0.59165988,\n", - " 0.5944257 , 0.59018699, 0.59180163, 0.59270748, 0.59015999,\n", - " 0.59451179, 0.5925782 , 0.59221837, 0.59452251, 0.59012406,\n", - " 0.59236712, 0.59213945, 0.59013148, 0.59450445, 0.59204248,\n", - " 0.59275305, 0.59448392, 0.51467256, 0.51681741, 0.59219055,\n", - " 0.59076114, 0.59387372, 0.59213029, 0.59261978, 0.59385979,\n", - " 0.59080955, 0.59274399, 0.59180476, 0.59083819],\n", - " [-1.26055967, -1.26031469, -1.2598023 , -1.26083158, -1.25981216,\n", - " -1.26160634, -1.26143231, -1.25920552, -1.26001143, -1.26105243,\n", - " -1.26026455, -1.26115601, -1.26145837, -1.25972946, -1.26035568,\n", - " -1.26035017, -1.25972691, -1.26145864, -1.2611518 , -1.26026992,\n", - " -1.28718859, -1.28580227, -1.29551207, -1.29591315, -1.26023455,\n", - " -1.26119118, -1.2611013 , -1.26019174, -1.26077014, -1.26007772,\n", - " -1.25987122, -1.260857 , -1.2609502 , -1.26075456, -1.26134921,\n", - " -1.25965397, -1.25996982, -1.26071455, -1.25976978, -1.26108183,\n", - " -1.26034272, -1.26107775, -1.26118967, -1.25922595, -1.25959951,\n", - " -1.26157551, -1.26072113, -1.26058879, -1.26193197, -1.25939288,\n", - " -1.28878699, -1.28913722, -1.26030408, -1.26116608, -1.26092241,\n", - " -1.25960761, -1.2599948 , -1.26128422, -1.26055622, -1.26012439,\n", - " -1.26052166, -1.2609009 , -0.49429912, -0.49420118, -0.49402884,\n", - " -0.49444032, -0.49392715, -0.49464442, -0.49464248, -0.49384803,\n", - " -0.4940031 , -0.49450349, -0.49410766, -0.49446404, -0.49461625,\n", - " -0.49392508, -0.49424032, -0.49423811, -0.49392437, -0.49461667,\n", - " -0.4944622 , -0.49410966, -0.49580432, -0.4952599 , -0.49584758,\n", - " -0.49606291, -0.49405329, -0.49450455, -0.4944593 , -0.49409569,\n", - " -0.49438685, -0.49411004, -0.49400608, -0.49449611, -0.4942921 ,\n", - " -0.49429813, -0.49459747, -0.49391976, -0.49407876, -0.49437648,\n", - " -0.49398646, -0.49451098, -0.49413892, -0.49443276, -0.49457543,\n", - " -0.49388618, -0.49382348, -0.49469773, -0.49426765, -0.49431067,\n", - " -0.49473566, -0.49380506, -0.49553592, -0.49574219, -0.49407224,\n", - " -0.4944902 , -0.4944531 , -0.49402325, -0.49396739, -0.49456722,\n", - " -0.49428634, -0.49420954, -0.49415863, -0.49439461]]), _tm=BetatronTuneMonitor(tune_h='srdiag/beam-tune/main/Qh', tune_v='srdiag/beam-tune/main/Qv', name='BETATRON_TUNE'), _quads=[Quadrupole(peer='Simulator:design', name='QD2E-C04', model_name='QD2E-C04', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C05', model_name='QD2A-C05', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C05', model_name='QD2E-C05', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C06', model_name='QD2A-C06', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C06', model_name='QD2E-C06', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C07', model_name='QD2A-C07', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C07', model_name='QD2E-C07', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C08', model_name='QD2A-C08', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C08', model_name='QD2E-C08', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C09', model_name='QD2A-C09', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C09', model_name='QD2E-C09', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C10', model_name='QD2A-C10', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C10', model_name='QD2E-C10', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C11', model_name='QD2A-C11', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C11', model_name='QD2E-C11', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C12', model_name='QD2A-C12', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C12', model_name='QD2E-C12', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C13', model_name='QD2A-C13', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C13', model_name='QD2E-C13', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C14', model_name='QD2A-C14', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C14', model_name='QD2E-C14', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C15', model_name='QD2A-C15', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C15', model_name='QD2E-C15', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C16', model_name='QD2A-C16', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C16', model_name='QD2E-C16', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C17', model_name='QD2A-C17', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C17', model_name='QD2E-C17', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C18', model_name='QD2A-C18', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C18', model_name='QD2E-C18', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C19', model_name='QD2A-C19', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C19', model_name='QD2E-C19', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C20', model_name='QD2A-C20', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C20', model_name='QD2E-C20', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C21', model_name='QD2A-C21', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C21', model_name='QD2E-C21', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C22', model_name='QD2A-C22', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C22', model_name='QD2E-C22', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C23', model_name='QD2A-C23', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C23', model_name='QD2E-C23', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C24', model_name='QD2A-C24', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C24', model_name='QD2E-C24', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C25', model_name='QD2A-C25', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C25', model_name='QD2E-C25', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C26', model_name='QD2A-C26', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C26', model_name='QD2E-C26', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C27', model_name='QD2A-C27', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C27', model_name='QD2E-C27', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C28', model_name='QD2A-C28', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C28', model_name='QD2E-C28', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C29', model_name='QD2A-C29', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C29', model_name='QD2E-C29', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C30', model_name='QD2A-C30', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C30', model_name='QD2E-C30', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C31', model_name='QD2A-C31', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C31', model_name='QD2E-C31', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C32', model_name='QD2A-C32', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C32', model_name='QD2E-C32', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C01', model_name='QD2A-C01', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C01', model_name='QD2E-C01', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C02', model_name='QD2A-C02', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2E-C02', model_name='QD2E-C02', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QD2A-C03', model_name='QD2A-C03', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C04', model_name='QF1E-C04', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C05', model_name='QF1A-C05', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C05', model_name='QF1E-C05', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C06', model_name='QF1A-C06', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C06', model_name='QF1E-C06', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C07', model_name='QF1A-C07', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C07', model_name='QF1E-C07', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C08', model_name='QF1A-C08', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C08', model_name='QF1E-C08', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C09', model_name='QF1A-C09', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C09', model_name='QF1E-C09', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C10', model_name='QF1A-C10', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C10', model_name='QF1E-C10', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C11', model_name='QF1A-C11', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C11', model_name='QF1E-C11', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C12', model_name='QF1A-C12', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C12', model_name='QF1E-C12', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C13', model_name='QF1A-C13', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C13', model_name='QF1E-C13', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C14', model_name='QF1A-C14', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C14', model_name='QF1E-C14', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C15', model_name='QF1A-C15', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C15', model_name='QF1E-C15', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C16', model_name='QF1A-C16', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C16', model_name='QF1E-C16', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C17', model_name='QF1A-C17', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C17', model_name='QF1E-C17', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C18', model_name='QF1A-C18', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C18', model_name='QF1E-C18', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C19', model_name='QF1A-C19', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C19', model_name='QF1E-C19', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C20', model_name='QF1A-C20', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C20', model_name='QF1E-C20', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C21', model_name='QF1A-C21', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C21', model_name='QF1E-C21', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C22', model_name='QF1A-C22', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C22', model_name='QF1E-C22', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C23', model_name='QF1A-C23', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C23', model_name='QF1E-C23', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C24', model_name='QF1A-C24', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C24', model_name='QF1E-C24', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C25', model_name='QF1A-C25', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C25', model_name='QF1E-C25', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C26', model_name='QF1A-C26', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C26', model_name='QF1E-C26', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C27', model_name='QF1A-C27', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C27', model_name='QF1E-C27', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C28', model_name='QF1A-C28', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C28', model_name='QF1E-C28', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C29', model_name='QF1A-C29', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C29', model_name='QF1E-C29', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C30', model_name='QF1A-C30', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C30', model_name='QF1E-C30', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C31', model_name='QF1A-C31', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C31', model_name='QF1E-C31', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C32', model_name='QF1A-C32', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C32', model_name='QF1E-C32', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C01', model_name='QF1A-C01', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C01', model_name='QF1E-C01', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C02', model_name='QF1A-C02', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1E-C02', model_name='QF1E-C02', magnet_model=LinearMagnetModel), Quadrupole(peer='Simulator:design', name='QF1A-C03', model_name='QF1A-C03', magnet_model=LinearMagnetModel)], name='DEFAULT_TUNE_CORRECTION')" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "print(f\"Tune before correction: {SR.tune.readback()}\")\n", - "\n", - "qx, qy = 0.19, 0.28\n", - "print(f\"\\nSetting tune to [{qx}, {qy}]\")\n", - "SR.tune.set([qx, qy], iter=10, wait_time=wait_time)\n", - "print(f\"Tune after correction: {SR.tune.readback()}\")\n", - "\n", - "qx, qy = 0.21, 0.30\n", - "print(f\"\\nSetting tune to [{qx}, {qy}]\")\n", - "SR.tune.set([qx, qy], iter=10, wait_time=wait_time)\n", - "print(f\"Tune after correction: {SR.tune.readback()}\")\n", - "\n", - "SR.tune # string representation" - ] + "outputs": [], + "source": "print(f\"Tune before correction: {SR.tool.tune.readback()}\")\n\nqx, qy = 0.19, 0.28\nprint(f\"\\nSetting tune to [{qx}, {qy}]\")\nSR.tool.tune.set([qx, qy], iter=10, wait_time=wait_time)\nprint(f\"Tune after correction: {SR.tool.tune.readback()}\")\n\nqx, qy = 0.21, 0.30\nprint(f\"\\nSetting tune to [{qx}, {qy}]\")\nSR.tool.tune.set([qx, qy], iter=10, wait_time=wait_time)\nprint(f\"Tune after correction: {SR.tool.tune.readback()}\")\n\nSR.tool.tune # string representation" }, { "cell_type": "code", diff --git a/examples/use_cases/01-tune_correction.py b/examples/use_cases/01-tune_correction.py index d77cb367..fc84e929 100644 --- a/examples/use_cases/01-tune_correction.py +++ b/examples/use_cases/01-tune_correction.py @@ -112,14 +112,14 @@ # ### Standard tune correction tool # -# `SR.tune` is the `DEFAULT_TUNE_CORRECTION` tool. `SR.trm` is the `DEFAULT_TUNE_RESPONSE_MATRIX` tool. +# `SR.tool.tune` is the `DEFAULT_TUNE_CORRECTION` tool. `SR.tool.trm` is the `DEFAULT_TUNE_RESPONSE_MATRIX` tool. # # Before correcting the tune you need a response matrix. It can be measured (below) or loaded from a previously saved file. # In[7]: -print(SR.tune) # string representation +print(SR.tool.tune) # string representation # #### Measuring the tune response matrix @@ -145,36 +145,36 @@ def tune_callback(action: int, cb_data: dict): # In[9]: -if SR.tune.response_matrix is None: - SR.trm.measure(sleep_between_step=wait_time, callback=tune_callback) - SR.trm.save(CONFIG_DIR / "trm.json") +if SR.tool.tune.response_matrix is None: + SR.tool.trm.measure(sleep_between_step=wait_time, callback=tune_callback) + SR.tool.trm.save(CONFIG_DIR / "trm.json") -SR.tune.load(CONFIG_DIR / "trm.json") +SR.tool.tune.load(CONFIG_DIR / "trm.json") print("Response matrix loaded.") -print(SR.trm) # string representation +print(SR.tool.trm) # string representation # #### Correcting the tune # -# `SR.tune.set([qx, qy])` runs the correction iteratively. The `iter` parameter controls +# `SR.tool.tune.set([qx, qy])` runs the correction iteratively. The `iter` parameter controls # the number of iterations and `wait_time` the settling time between each iteration. # In[10]: -print(f"Tune before correction: {SR.tune.readback()}") +print(f"Tune before correction: {SR.tool.tune.readback()}") qx, qy = 0.19, 0.28 print(f"\nSetting tune to [{qx}, {qy}]") -SR.tune.set([qx, qy], iter=10, wait_time=wait_time) -print(f"Tune after correction: {SR.tune.readback()}") +SR.tool.tune.set([qx, qy], iter=10, wait_time=wait_time) +print(f"Tune after correction: {SR.tool.tune.readback()}") qx, qy = 0.21, 0.30 print(f"\nSetting tune to [{qx}, {qy}]") -SR.tune.set([qx, qy], iter=10, wait_time=wait_time) -print(f"Tune after correction: {SR.tune.readback()}") +SR.tool.tune.set([qx, qy], iter=10, wait_time=wait_time) +print(f"Tune after correction: {SR.tool.tune.readback()}") -print(SR.tune) # string representation +print(SR.tool.tune) # string representation # In[ ]: diff --git a/examples/use_cases/03-orbit_correction.ipynb b/examples/use_cases/03-orbit_correction.ipynb index 58cad3cc..9121fd64 100644 --- a/examples/use_cases/03-orbit_correction.ipynb +++ b/examples/use_cases/03-orbit_correction.ipynb @@ -195,140 +195,23 @@ "cell_type": "markdown", "id": "808334df", "metadata": {}, - "source": [ - "### Orbit response matrix\n", - "\n", - "The ORM can be saved in multiple formats. Load it into `SR.orbit` before correcting." - ] + "source": "### Orbit response matrix\n\nThe ORM can be saved in multiple formats. Load it into `SR.tool.orbit` before correcting." }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "33636af2", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "OrbitResponseMatrix(bpm_array_name='BPM', hcorr_array_name='HCorr', vcorr_array_name='VCorr', corrector_delta=1e-06, n_step=1, sleep_between_step=0.0, n_avg_meas=1, sleep_between_meas=0.0, name='DEFAULT_ORBIT_RESPONSE_MATRIX')" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "SR.orm # string representation" - ] + "outputs": [], + "source": "SR.tool.orm # string representation" }, { "cell_type": "code", "execution_count": null, "id": "3f9dcb18", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "14 Sep 2026, 15:10:12 | INFO | Measuring correctors from the default arrays: HCorr and VCorr.\n", - "14 Sep 2026, 15:10:14 | INFO | Measured response of SH1A-C04-H.\n", - "14 Sep 2026, 15:10:16 | INFO | Measured response of SJ1A-C04-H.\n", - "14 Sep 2026, 15:10:17 | INFO | Measured response of SJ2A-C04-H.\n", - "14 Sep 2026, 15:10:18 | INFO | Measured response of SJ1B-C04-H.\n", - "14 Sep 2026, 15:10:19 | INFO | Measured response of SH2B-C04-H.\n", - "14 Sep 2026, 15:10:21 | INFO | Measured response of SJ1D-C04-H.\n", - "14 Sep 2026, 15:10:22 | INFO | Measured response of SJ2E-C04-H.\n", - "14 Sep 2026, 15:10:23 | INFO | Measured response of SJ1E-C04-H.\n", - "14 Sep 2026, 15:10:24 | INFO | Measured response of SH3E-C04-H.\n", - "14 Sep 2026, 15:10:26 | INFO | Measured response of SH1A-C05-H.\n", - "14 Sep 2026, 15:10:27 | INFO | Measured response of SD1A-C05-H.\n", - "14 Sep 2026, 15:10:28 | INFO | Measured response of SF2A-C05-H.\n", - "14 Sep 2026, 15:10:29 | INFO | Measured response of SD1B-C05-H.\n", - "14 Sep 2026, 15:10:31 | INFO | Measured response of SH2B-C05-H.\n", - "14 Sep 2026, 15:10:32 | INFO | Measured response of SD1D-C05-H.\n", - "14 Sep 2026, 15:10:33 | INFO | Measured response of SF2E-C05-H.\n", - "14 Sep 2026, 15:10:34 | INFO | Measured response of SD1E-C05-H.\n", - "14 Sep 2026, 15:10:36 | INFO | Measured response of SH3E-C05-H.\n", - "14 Sep 2026, 15:10:37 | INFO | Measured response of SH1A-C06-H.\n", - "14 Sep 2026, 15:10:38 | INFO | Measured response of SD1A-C06-H.\n", - "14 Sep 2026, 15:10:39 | INFO | Measured response of SF2A-C06-H.\n", - "14 Sep 2026, 15:10:41 | INFO | Measured response of SD1B-C06-H.\n", - "14 Sep 2026, 15:10:42 | INFO | Measured response of SH2B-C06-H.\n", - "14 Sep 2026, 15:10:43 | INFO | Measured response of SD1D-C06-H.\n", - "14 Sep 2026, 15:10:44 | INFO | Measured response of SF2E-C06-H.\n", - "14 Sep 2026, 15:10:45 | INFO | Measured response of SD1E-C06-H.\n", - "14 Sep 2026, 15:10:47 | INFO | Measured response of SH3E-C06-H.\n", - "14 Sep 2026, 15:10:48 | INFO | Measured response of SH1A-C07-H.\n", - "14 Sep 2026, 15:10:49 | INFO | Measured response of SD1A-C07-H.\n", - "14 Sep 2026, 15:10:50 | INFO | Measured response of SF2A-C07-H.\n", - "14 Sep 2026, 15:10:52 | INFO | Measured response of SD1B-C07-H.\n", - "14 Sep 2026, 15:10:53 | INFO | Measured response of SH2B-C07-H.\n", - "14 Sep 2026, 15:10:54 | INFO | Measured response of SD1D-C07-H.\n", - "14 Sep 2026, 15:10:55 | INFO | Measured response of SF2E-C07-H.\n", - "14 Sep 2026, 15:10:56 | INFO | Measured response of SD1E-C07-H.\n", - "14 Sep 2026, 15:10:57 | INFO | Measured response of SH3E-C07-H.\n", - "14 Sep 2026, 15:10:59 | INFO | Measured response of SH1A-C08-H.\n", - "14 Sep 2026, 15:11:00 | INFO | Measured response of SD1A-C08-H.\n", - "14 Sep 2026, 15:11:01 | INFO | Measured response of SF2A-C08-H.\n", - "14 Sep 2026, 15:11:02 | INFO | Measured response of SD1B-C08-H.\n", - "14 Sep 2026, 15:11:04 | INFO | Measured response of SH2B-C08-H.\n", - "14 Sep 2026, 15:11:05 | INFO | Measured response of SD1D-C08-H.\n", - "14 Sep 2026, 15:11:06 | INFO | Measured response of SF2E-C08-H.\n", - "14 Sep 2026, 15:11:07 | INFO | Measured response of SD1E-C08-H.\n", - "14 Sep 2026, 15:11:08 | INFO | Measured response of SH3E-C08-H.\n", - "14 Sep 2026, 15:11:10 | INFO | Measured response of SH1A-C09-H.\n", - "14 Sep 2026, 15:11:11 | INFO | Measured response of SD1A-C09-H.\n", - "14 Sep 2026, 15:11:12 | INFO | Measured response of SF2A-C09-H.\n", - "14 Sep 2026, 15:11:13 | INFO | Measured response of SD1B-C09-H.\n", - "14 Sep 2026, 15:11:14 | INFO | Measured response of SH2B-C09-H.\n", - "14 Sep 2026, 15:11:16 | INFO | Measured response of SD1D-C09-H.\n", - "14 Sep 2026, 15:11:17 | INFO | Measured response of SF2E-C09-H.\n", - "14 Sep 2026, 15:11:18 | INFO | Measured response of SD1E-C09-H.\n", - "14 Sep 2026, 15:11:19 | INFO | Measured response of SH3E-C09-H.\n", - "14 Sep 2026, 15:11:21 | INFO | Measured response of SH1A-C10-H.\n", - "14 Sep 2026, 15:11:22 | INFO | Measured response of SD1A-C10-H.\n", - "14 Sep 2026, 15:11:23 | INFO | Measured response of SF2A-C10-H.\n", - "14 Sep 2026, 15:11:24 | INFO | Measured response of SD1B-C10-H.\n", - "14 Sep 2026, 15:11:26 | INFO | Measured response of SH2B-C10-H.\n", - "14 Sep 2026, 15:11:27 | INFO | Measured response of SD1D-C10-H.\n", - "14 Sep 2026, 15:11:28 | INFO | Measured response of SF2E-C10-H.\n", - "14 Sep 2026, 15:11:29 | INFO | Measured response of SD1E-C10-H.\n", - "14 Sep 2026, 15:11:31 | INFO | Measured response of SH3E-C10-H.\n", - "14 Sep 2026, 15:11:32 | INFO | Measured response of SH1A-C11-H.\n", - "14 Sep 2026, 15:11:33 | INFO | Measured response of SD1A-C11-H.\n", - "14 Sep 2026, 15:11:34 | INFO | Measured response of SF2A-C11-H.\n", - "14 Sep 2026, 15:11:35 | INFO | Measured response of SD1B-C11-H.\n", - "14 Sep 2026, 15:11:37 | INFO | Measured response of SH2B-C11-H.\n", - "14 Sep 2026, 15:11:38 | INFO | Measured response of SD1D-C11-H.\n", - "14 Sep 2026, 15:11:39 | INFO | Measured response of SF2E-C11-H.\n", - "14 Sep 2026, 15:11:40 | INFO | Measured response of SD1E-C11-H.\n", - "14 Sep 2026, 15:11:41 | INFO | Measured response of SH3E-C11-H.\n", - "14 Sep 2026, 15:11:43 | INFO | Measured response of SH1A-C12-H.\n", - "14 Sep 2026, 15:11:44 | INFO | Measured response of SD1A-C12-H.\n", - "14 Sep 2026, 15:11:45 | INFO | Measured response of SF2A-C12-H.\n", - "14 Sep 2026, 15:11:46 | INFO | Measured response of SD1B-C12-H.\n", - "14 Sep 2026, 15:11:47 | INFO | Measured response of SH2B-C12-H.\n", - "14 Sep 2026, 15:11:49 | INFO | Measured response of SD1D-C12-H.\n", - "14 Sep 2026, 15:11:50 | INFO | Measured response of SF2E-C12-H.\n", - "14 Sep 2026, 15:11:51 | INFO | Measured response of SD1E-C12-H.\n", - "14 Sep 2026, 15:11:52 | INFO | Measured response of SH3E-C12-H.\n", - "14 Sep 2026, 15:11:54 | INFO | Measured response of SH1A-C13-H.\n", - "14 Sep 2026, 15:11:55 | INFO | Measured response of SD1A-C13-H.\n", - "14 Sep 2026, 15:11:56 | INFO | Measured response of SF2A-C13-H.\n" - ] - } - ], - "source": [ - "SR.orm.measure(sleep_between_step=wait_time)\n", - "SR.orm.save(CONFIG_DIR / \"orm.json\")\n", - "SR.orm.save(CONFIG_DIR / \"orm.yaml\", with_type=\"yaml\")\n", - "SR.orm.save(CONFIG_DIR / \"orm.npz\", with_type=\"npz\")\n", - "\n", - "SR.orbit.load(CONFIG_DIR / \"orm.json\")\n", - "print(\"ORM measured, saved and loaded.\")" - ] + "outputs": [], + "source": "SR.tool.orm.measure(sleep_between_step=wait_time)\nSR.tool.orm.save(CONFIG_DIR / \"orm.json\")\nSR.tool.orm.save(CONFIG_DIR / \"orm.yaml\", with_type=\"yaml\")\nSR.tool.orm.save(CONFIG_DIR / \"orm.npz\", with_type=\"npz\")\n\nSR.tool.orbit.load(CONFIG_DIR / \"orm.json\")\nprint(\"ORM measured, saved and loaded.\")" }, { "cell_type": "markdown", @@ -344,15 +227,7 @@ "id": "942f8f3b", "metadata": {}, "outputs": [], - "source": [ - "orm_data = SR.orm.get()\n", - "plt.imshow(np.array(orm_data[\"matrix\"]))\n", - "plt.colorbar()\n", - "plt.title(\"Orbit response matrix\")\n", - "plt.xlabel(\"Corrector index\")\n", - "plt.ylabel(\"BPM index\")\n", - "plt.show()" - ] + "source": "orm_data = SR.tool.orm.get()\nplt.imshow(np.array(orm_data[\"matrix\"]))\nplt.colorbar()\nplt.title(\"Orbit response matrix\")\nplt.xlabel(\"Corrector index\")\nplt.ylabel(\"BPM index\")\nplt.show()" }, { "cell_type": "markdown", @@ -370,28 +245,7 @@ "id": "693fc409", "metadata": {}, "outputs": [], - "source": [ - "# Uncomment to measure and visualise dispersion\n", - "\n", - "# def disp_callback(action: int, cb_data) -> bool:\n", - "# from pyaml.common.constants import Action\n", - "# if action == Action.APPLY:\n", - "# print(\"Changing RF frequency\")\n", - "# elif action == Action.MEASURE:\n", - "# print(\"Reading orbit\")\n", - "# elif action == Action.RESTORE:\n", - "# print(\"Restoring RF frequency\")\n", - "# return True\n", - "\n", - "# SR.dispersion.measure(callback=disp_callback)\n", - "# disp_data = SR.dispersion.get()\n", - "# plt.plot(disp_data[\"frequency_response_x\"], label=\"H dispersion\")\n", - "# plt.plot(disp_data[\"frequency_response_y\"], label=\"V dispersion\")\n", - "# plt.xlabel(\"BPM index\")\n", - "# plt.ylabel(\"Orbit / Δf [m/Hz]\")\n", - "# plt.legend()\n", - "# plt.show()" - ] + "source": "# Uncomment to measure and visualise dispersion\n\n# def disp_callback(action: int, cb_data) -> bool:\n# from pyaml.common.constants import Action\n# if action == Action.APPLY:\n# print(\"Changing RF frequency\")\n# elif action == Action.MEASURE:\n# print(\"Reading orbit\")\n# elif action == Action.RESTORE:\n# print(\"Restoring RF frequency\")\n# return True\n\n# SR.tool.dispersion.measure(callback=disp_callback)\n# disp_data = SR.tool.dispersion.get()\n# plt.plot(disp_data[\"frequency_response_x\"], label=\"H dispersion\")\n# plt.plot(disp_data[\"frequency_response_y\"], label=\"V dispersion\")\n# plt.xlabel(\"BPM index\")\n# plt.ylabel(\"Orbit / Δf [m/Hz]\")\n# plt.legend()\n# plt.show()" }, { "cell_type": "markdown", @@ -464,16 +318,7 @@ "cell_type": "markdown", "id": "cf49c543", "metadata": {}, - "source": [ - "### Correct the orbit\n", - "\n", - "Standard correction: `SR.orbit.correct(reference=reference)`.\n", - "\n", - "Optional variants (uncomment to use):\n", - "- **Virtual corrector weight**: down-weights correctors that are already at large strengths.\n", - "- **RF correction**: also adjusts the RF frequency to minimise dispersion-driven orbit.\n", - "- **Live unit mismatch**: if BESSY2 BPMs read in nm, pass `gain=1e-9`." - ] + "source": "### Correct the orbit\n\nStandard correction: `SR.tool.orbit.correct(reference=reference)`.\n\nOptional variants (uncomment to use):\n- **Virtual corrector weight**: down-weights correctors that are already at large strengths.\n- **RF correction**: also adjusts the RF frequency to minimise dispersion-driven orbit.\n- **Live unit mismatch**: if BESSY2 BPMs read in nm, pass `gain=1e-9`." }, { "cell_type": "code", @@ -481,22 +326,7 @@ "id": "93135a4c", "metadata": {}, "outputs": [], - "source": [ - "# Standard correction\n", - "SR.orbit.correct(reference=reference)\n", - "\n", - "# With virtual corrector weight (ESRF style):\n", - "# SR.orbit.set_virtual_weight(1000)\n", - "# SR.orbit.correct(reference=reference)\n", - "\n", - "# With RF orbit correction (ESRF style, requires dispersion in ORM):\n", - "# SR.orbit.correct(reference=reference, rf=True)\n", - "\n", - "# BESSY2 live mode — BPMs in nm, use gain to compensate unit mismatch:\n", - "# SR.orbit.correct(reference=reference, gain=1e-9)\n", - "\n", - "sleep(wait_time)" - ] + "source": "# Standard correction\nSR.tool.orbit.correct(reference=reference)\n\n# With virtual corrector weight (ESRF style):\n# SR.tool.orbit.set_virtual_weight(1000)\n# SR.tool.orbit.correct(reference=reference)\n\n# With RF orbit correction (ESRF style, requires dispersion in ORM):\n# SR.tool.orbit.correct(reference=reference, rf=True)\n\n# BESSY2 live mode — BPMs in nm, use gain to compensate unit mismatch:\n# SR.tool.orbit.correct(reference=reference, gain=1e-9)\n\nsleep(wait_time)" }, { "cell_type": "code", diff --git a/examples/use_cases/03-orbit_correction.py b/examples/use_cases/03-orbit_correction.py index b7af039c..7271f26f 100644 --- a/examples/use_cases/03-orbit_correction.py +++ b/examples/use_cases/03-orbit_correction.py @@ -92,23 +92,23 @@ # ### Orbit response matrix # -# The ORM can be saved in multiple formats. Load it into `SR.orbit` before correcting. +# The ORM can be saved in multiple formats. Load it into `SR.tool.orbit` before correcting. # In[5]: -print(SR.orm) # string representation +print(SR.tool.orm) # string representation # In[6]: -SR.orm.measure(sleep_between_step=wait_time) -SR.orm.save(CONFIG_DIR / "orm.json") -SR.orm.save(CONFIG_DIR / "orm.yaml", with_type="yaml") -SR.orm.save(CONFIG_DIR / "orm.npz", with_type="npz") +SR.tool.orm.measure(sleep_between_step=wait_time) +SR.tool.orm.save(CONFIG_DIR / "orm.json") +SR.tool.orm.save(CONFIG_DIR / "orm.yaml", with_type="yaml") +SR.tool.orm.save(CONFIG_DIR / "orm.npz", with_type="npz") -SR.orbit.load(CONFIG_DIR / "orm.json") +SR.tool.orbit.load(CONFIG_DIR / "orm.json") print("ORM measured, saved and loaded.") @@ -117,7 +117,7 @@ # In[7]: -orm_data = SR.orm.get() +orm_data = SR.tool.orm.get() plt.imshow(np.array(orm_data["matrix"])) plt.colorbar() plt.title("Orbit response matrix") @@ -147,8 +147,8 @@ # print("Restoring RF frequency") # return True -# SR.dispersion.measure(callback=disp_callback) -# disp_data = SR.dispersion.get() +# SR.tool.dispersion.measure(callback=disp_callback) +# disp_data = SR.tool.dispersion.get() # plt.plot(disp_data["frequency_response_x"], label="H dispersion") # plt.plot(disp_data["frequency_response_y"], label="V dispersion") # plt.xlabel("BPM index") @@ -202,7 +202,7 @@ # ### Correct the orbit # -# Standard correction: `SR.orbit.correct(reference=reference)`. +# Standard correction: `SR.tool.orbit.correct(reference=reference)`. # # Optional variants (uncomment to use): # - **Virtual corrector weight**: down-weights correctors that are already at large strengths. @@ -213,17 +213,17 @@ # Standard correction -SR.orbit.correct(reference=reference) +SR.tool.orbit.correct(reference=reference) # With virtual corrector weight (ESRF style): -# SR.orbit.set_virtual_weight(1000) -# SR.orbit.correct(reference=reference) +# SR.tool.orbit.set_virtual_weight(1000) +# SR.tool.orbit.correct(reference=reference) # With RF orbit correction (ESRF style, requires dispersion in ORM): -# SR.orbit.correct(reference=reference, rf=True) +# SR.tool.orbit.correct(reference=reference, rf=True) # BESSY2 live mode — BPMs in nm, use gain to compensate unit mismatch: -# SR.orbit.correct(reference=reference, gain=1e-9) +# SR.tool.orbit.correct(reference=reference, gain=1e-9) sleep(wait_time) diff --git a/examples/use_cases/README.md b/examples/use_cases/README.md index ebcfe8c9..6bc3076e 100644 --- a/examples/use_cases/README.md +++ b/examples/use_cases/README.md @@ -89,8 +89,8 @@ In `design` mode all wait times can be set to `0.0`. In `live` mode use `wait_ti | `BPM` | Beam position monitors | | `BETATRON_TUNE` | Betatron tune monitor | | `CHROMATICITY_MONITOR` | Chromaticity monitor | -| `DEFAULT_TUNE_CORRECTION` | Tune correction tool → `SR.tune` | -| `DEFAULT_TUNE_RESPONSE_MATRIX` | Tune response matrix tool → `SR.trm` | -| `DEFAULT_ORBIT_CORRECTION` | Orbit correction tool → `SR.orbit` | -| `DEFAULT_ORBIT_RESPONSE_MATRIX` | Orbit response matrix tool → `SR.orm` | -| `DEFAULT_DISPERSION` | Dispersion measurement tool → `SR.dispersion` | +| `DEFAULT_TUNE_CORRECTION` | Tune correction tool → `SR.tool.tune` | +| `DEFAULT_TUNE_RESPONSE_MATRIX` | Tune response matrix tool → `SR.tool.trm` | +| `DEFAULT_ORBIT_CORRECTION` | Orbit correction tool → `SR.tool.orbit` | +| `DEFAULT_ORBIT_RESPONSE_MATRIX` | Orbit response matrix tool → `SR.tool.orm` | +| `DEFAULT_DISPERSION` | Dispersion measurement tool → `SR.tool.dispersion` | diff --git a/pyaml/common/holders/element_holder.py b/pyaml/common/holders/element_holder.py index 5eed50c3..21d303ac 100644 --- a/pyaml/common/holders/element_holder.py +++ b/pyaml/common/holders/element_holder.py @@ -66,11 +66,6 @@ class ElementHolder(metaclass=ABCMeta): Diagnostics of this mode, with typed default-name access. tool Tuning and measurement tools of this mode, with typed default-name access. - tune, chromaticity, orbit, dispersion - Backward-compatible aliases for ``tool.tune``, ``tool.chromaticity``, ``tool.orbit`` - and ``tool.dispersion``. - trm, crm, orm - Backward-compatible aliases for ``tool.trm``, ``tool.crm`` and ``tool.orm``. Methods ------- @@ -695,16 +690,6 @@ def get_crm_tuning(self, name: str) -> "ChromaticityResponseMatrix": """ return self._get("ChromaticityResponseMatrix tool", name, self._TOOLS) - @property - def chromaticity(self) -> "Chromaticity": - """Return the chromaticity. Alias for ``tool.chromaticity``.""" - return self.tool.chromaticity - - @property - def crm(self) -> "ChromaticityResponseMatrix": - """Return the crm. Alias for ``tool.crm``.""" - return self.tool.crm - # ---- Tune --------------------------------------------------------- def get_tune_tuning(self, name: str) -> "Tune": @@ -723,11 +708,6 @@ def get_tune_tuning(self, name: str) -> "Tune": """ return self._get("Tune tuning tool", name, self._TOOLS) - @property - def tune(self) -> "Tune": - """Return the tune. Alias for ``tool.tune``.""" - return self.tool.tune - def get_trm_tuning(self, name: str) -> "TuneResponseMatrix": """ Return a named tune response-matrix tool. @@ -744,11 +724,6 @@ def get_trm_tuning(self, name: str) -> "TuneResponseMatrix": """ return self._get("TuneResponseMatrix tool", name, self._TOOLS) - @property - def trm(self) -> "TuneResponseMatrix": - """Return the default tune response-matrix tool. Alias for ``tool.trm``.""" - return self.tool.trm - # ---- Orbit -------------------------------------------------------- def get_orbit_tuning(self, name: str) -> "Orbit": @@ -767,11 +742,6 @@ def get_orbit_tuning(self, name: str) -> "Orbit": """ return self._get("Orbit tuning tool", name, self._TOOLS) - @property - def orbit(self) -> "Orbit": - """Return the orbit. Alias for ``tool.orbit``.""" - return self.tool.orbit - def get_orm_tuning(self, name: str) -> "OrbitResponseMatrix": """ Return a named orbit response-matrix tool. @@ -788,11 +758,6 @@ def get_orm_tuning(self, name: str) -> "OrbitResponseMatrix": """ return self._get("OrbitResponseMatrix tool", name, self._TOOLS) - @property - def orm(self) -> "OrbitResponseMatrix": - """Return the default orbit response-matrix tool. Alias for ``tool.orm``.""" - return self.tool.orm - # ---- BBA -------------------------------------------------------- def get_bba(self, name: str) -> "BBA": @@ -829,11 +794,6 @@ def get_dispersion_tuning(self, name: str) -> "Dispersion": """ return self._get("Dispersion tool", name, self._TOOLS) - @property - def dispersion(self) -> "Dispersion": - """Return the dispersion. Alias for ``tool.dispersion``.""" - return self.tool.dispersion - def _get_array(self, name: str): """ Generic array resolver used by YellowPages. diff --git a/pyaml/common/holders/tool_holder.py b/pyaml/common/holders/tool_holder.py index 2ac49e86..9f861959 100644 --- a/pyaml/common/holders/tool_holder.py +++ b/pyaml/common/holders/tool_holder.py @@ -40,12 +40,6 @@ class ToolHolder: get(name=None) Return a named tool, or all configured tools when no name is given. - Notes - ----- - :attr:`ElementHolder.tune `, - ``.trm``, ``.orbit``, ``.orm``, ``.chromaticity``, ``.crm`` and ``.dispersion`` are - backward-compatible aliases for the corresponding properties here. - Examples -------- >>> tune_correction = sr.live.tool.tune @@ -137,7 +131,7 @@ def chromaticity(self) -> "Chromaticity": from ...tuning_tools.chromaticity import Chromaticity name = "DEFAULT_CHROMATICITY_CORRECTION" - return self._validate_type(name, self._peer.get_chromaticity_tuning(name), Chromaticity) + return self._validate_type(name, self.get(name), Chromaticity) @property def crm(self) -> "ChromaticityResponseMatrix": @@ -145,7 +139,7 @@ def crm(self) -> "ChromaticityResponseMatrix": from ...tuning_tools.chromaticity_response_matrix import ChromaticityResponseMatrix name = "DEFAULT_CHROMATICITY_RESPONSE_MATRIX" - return self._validate_type(name, self._peer.get_crm_tuning(name), ChromaticityResponseMatrix) + return self._validate_type(name, self.get(name), ChromaticityResponseMatrix) @property def tune(self) -> "Tune": @@ -153,7 +147,7 @@ def tune(self) -> "Tune": from ...tuning_tools.tune import Tune name = "DEFAULT_TUNE_CORRECTION" - return self._validate_type(name, self._peer.get_tune_tuning(name), Tune) + return self._validate_type(name, self.get(name), Tune) @property def trm(self) -> "TuneResponseMatrix": @@ -161,7 +155,7 @@ def trm(self) -> "TuneResponseMatrix": from ...tuning_tools.tune_response_matrix import TuneResponseMatrix name = "DEFAULT_TUNE_RESPONSE_MATRIX" - return self._validate_type(name, self._peer.get_trm_tuning(name), TuneResponseMatrix) + return self._validate_type(name, self.get(name), TuneResponseMatrix) @property def orbit(self) -> "Orbit": @@ -169,7 +163,7 @@ def orbit(self) -> "Orbit": from ...tuning_tools.orbit import Orbit name = "DEFAULT_ORBIT_CORRECTION" - return self._validate_type(name, self._peer.get_orbit_tuning(name), Orbit) + return self._validate_type(name, self.get(name), Orbit) @property def orm(self) -> "OrbitResponseMatrix": @@ -177,7 +171,7 @@ def orm(self) -> "OrbitResponseMatrix": from ...tuning_tools.orbit_response_matrix import OrbitResponseMatrix name = "DEFAULT_ORBIT_RESPONSE_MATRIX" - return self._validate_type(name, self._peer.get_orm_tuning(name), OrbitResponseMatrix) + return self._validate_type(name, self.get(name), OrbitResponseMatrix) @property def dispersion(self) -> "Dispersion": @@ -185,7 +179,7 @@ def dispersion(self) -> "Dispersion": from ...tuning_tools.dispersion import Dispersion name = "DEFAULT_DISPERSION" - return self._validate_type(name, self._peer.get_dispersion_tuning(name), Dispersion) + return self._validate_type(name, self.get(name), Dispersion) def __repr__(self): return __pyaml_repr__(self) diff --git a/pyaml/tuning_tools/chromaticity_response_matrix.py b/pyaml/tuning_tools/chromaticity_response_matrix.py index c75ec53c..4d739e0e 100644 --- a/pyaml/tuning_tools/chromaticity_response_matrix.py +++ b/pyaml/tuning_tools/chromaticity_response_matrix.py @@ -151,10 +151,10 @@ def callback(action: Action, data:dict): sr = Accelerator.load("tests/config/EBSOrbit.yaml") acc = sr.design - if acc.crm.measure(callback=callback): - acc.crm.save("ideal_crm.json") - acc.crm.save("ideal_crm.yaml", with_type="yaml") - acc.crm.save("ideal_crm.npz", with_type="npz") + if acc.tool.crm.measure(callback=callback): + acc.tool.crm.save("ideal_crm.json") + acc.tool.crm.save("ideal_crm.yaml", with_type="yaml") + acc.tool.crm.save("ideal_crm.npz", with_type="npz") Parameters ---------- diff --git a/pyaml/tuning_tools/measurement_tool.py b/pyaml/tuning_tools/measurement_tool.py index 646d38ed..acdc5789 100644 --- a/pyaml/tuning_tools/measurement_tool.py +++ b/pyaml/tuning_tools/measurement_tool.py @@ -182,7 +182,7 @@ def callback(action: Action, data: dict): return True # Measure a tune response matrix using the above callback - sr.design.trm.measure(callback=callback) + sr.design.tool.trm.measure(callback=callback) Parameters ---------- diff --git a/pyaml/tuning_tools/orbit_response_matrix.py b/pyaml/tuning_tools/orbit_response_matrix.py index 101a7220..14bd96d2 100644 --- a/pyaml/tuning_tools/orbit_response_matrix.py +++ b/pyaml/tuning_tools/orbit_response_matrix.py @@ -173,10 +173,10 @@ def measure( sr = Accelerator.load("MyAccelerator.yaml") acc = sr.design - if acc.orm.measure(): - acc.orm.save("ideal_orm.json") - acc.orm.save("ideal_orm.yaml", with_type="yaml") - acc.orm.save("ideal_orm.npz", with_type="npz") + if acc.tool.orm.measure(): + acc.tool.orm.save("ideal_orm.json") + acc.tool.orm.save("ideal_orm.yaml", with_type="yaml") + acc.tool.orm.save("ideal_orm.npz", with_type="npz") Parameters ---------- diff --git a/pyaml/tuning_tools/tune_response_matrix.py b/pyaml/tuning_tools/tune_response_matrix.py index 0af72d6e..380a4105 100644 --- a/pyaml/tuning_tools/tune_response_matrix.py +++ b/pyaml/tuning_tools/tune_response_matrix.py @@ -190,10 +190,10 @@ def callback(action: Action, data:dict): sr = Accelerator.load("tests/config/EBSTune.yaml") acc = sr.design - if acc.trm.measure(n_avg_meas=3,sleep_between_meas=5,callback=callback): - acc.trm.save("ideal_trm.json") - acc.trm.save("ideal_trm.yaml", with_type="yaml") - acc.trm.save("ideal_trm.npz", with_type="npz") + if acc.tool.trm.measure(n_avg_meas=3,sleep_between_meas=5,callback=callback): + acc.tool.trm.save("ideal_trm.json") + acc.tool.trm.save("ideal_trm.yaml", with_type="yaml") + acc.tool.trm.save("ideal_trm.npz", with_type="npz") Parameters ---------- diff --git a/tests/diagnostics/test_diagnostic_accessors.py b/tests/diagnostics/test_diagnostic_accessors.py index 2acdb9c3..b4c6f58b 100644 --- a/tests/diagnostics/test_diagnostic_accessors.py +++ b/tests/diagnostics/test_diagnostic_accessors.py @@ -54,7 +54,7 @@ def test_diagnostic_raises_when_default_wrong_type(): ).design # Swap the registered default so it points to an object of the wrong type. - design._DIAG["BETATRON_TUNE"] = design.orbit + design._DIAG["BETATRON_TUNE"] = design.tool.orbit with pytest.raises(PyAMLException) as exc: _ = design.diagnostic.betatron_tune diff --git a/tests/tuning_tools/test_tool_accessors.py b/tests/tuning_tools/test_tool_accessors.py index d9a54e7b..a5dd2500 100644 --- a/tests/tuning_tools/test_tool_accessors.py +++ b/tests/tuning_tools/test_tool_accessors.py @@ -15,36 +15,36 @@ def test_tuning_tools_expose_configured_elements(): tune_monitor = design.get_betatron_tune_monitor("BETATRON_TUNE") quadrupoles = design.magnets.get("QForTune") - assert design.tune.tune_monitor is tune_monitor - assert design.tune.quadrupoles is quadrupoles - assert design.trm.tune_monitor is tune_monitor - assert design.trm.quadrupoles is quadrupoles + assert design.tool.tune.tune_monitor is tune_monitor + assert design.tool.tune.quadrupoles is quadrupoles + assert design.tool.trm.tune_monitor is tune_monitor + assert design.tool.trm.quadrupoles is quadrupoles chromaticity_monitor = design.get_chromaticity_monitor("CHROMATICITY_MONITOR") sextupoles = design.magnets.get("Sext") - assert design.chromaticity.chromaticity_monitor is chromaticity_monitor - assert design.chromaticity.sextupoles is sextupoles - assert design.crm.chromaticity_monitor is chromaticity_monitor - assert design.crm.sextupoles is sextupoles + assert design.tool.chromaticity.chromaticity_monitor is chromaticity_monitor + assert design.tool.chromaticity.sextupoles is sextupoles + assert design.tool.crm.chromaticity_monitor is chromaticity_monitor + assert design.tool.crm.sextupoles is sextupoles bpms = design.diagnostic.bpms.get("BPM") rf_plant = design.rf.get("DEFAULT_RF_PLANT") assert chromaticity_monitor.tune_monitor is tune_monitor assert chromaticity_monitor.bpms is bpms assert chromaticity_monitor.rf_plant is rf_plant - assert design.dispersion.bpms is bpms - assert design.dispersion.rf_plant is rf_plant + assert design.tool.dispersion.bpms is bpms + assert design.tool.dispersion.rf_plant is rf_plant hcorrectors = design.magnets.get("HCorr") vcorrectors = design.magnets.get("VCorr") - assert design.orbit.bpms is bpms - assert design.orbit.hcorrectors is hcorrectors - assert design.orbit.vcorrectors is vcorrectors - assert design.orbit.correctors.names() == hcorrectors.names() + vcorrectors.names() - assert design.orbit.rf_plant is rf_plant - assert design.orm.bpms is bpms - assert design.orm.hcorrectors is hcorrectors - assert design.orm.vcorrectors is vcorrectors + assert design.tool.orbit.bpms is bpms + assert design.tool.orbit.hcorrectors is hcorrectors + assert design.tool.orbit.vcorrectors is vcorrectors + assert design.tool.orbit.correctors.names() == hcorrectors.names() + vcorrectors.names() + assert design.tool.orbit.rf_plant is rf_plant + assert design.tool.orm.bpms is bpms + assert design.tool.orm.hcorrectors is hcorrectors + assert design.tool.orm.vcorrectors is vcorrectors for name in ("BBA-BPM_C04-04", "BBA2-BPM_C04-04"): bba = design.get_bba(name) @@ -54,7 +54,7 @@ def test_tuning_tools_expose_configured_elements(): assert bba.vcorrector is design.magnet.get("SD1A-C26-V") assert bba.quadrupole is design.magnet.get("QF6B-C04") - assert design.get_bba("BBA2-BPM_C04-04").tune_correction is design.tune + assert design.get_bba("BBA2-BPM_C04-04").tune_correction is design.tool.tune def test_tool_get_returns_named_tool(): @@ -64,8 +64,8 @@ def test_tool_get_returns_named_tool(): include_locations=False, ).design - assert design.tool.get("DEFAULT_TUNE_CORRECTION") is design.tune - assert design.tool.get("DEFAULT_ORBIT_RESPONSE_MATRIX") is design.orm + assert design.tool.get("DEFAULT_TUNE_CORRECTION") is design.tool.tune + assert design.tool.get("DEFAULT_ORBIT_RESPONSE_MATRIX") is design.tool.orm def test_tool_get_with_no_name_returns_all_configured_tools(): @@ -76,29 +76,13 @@ def test_tool_get_with_no_name_returns_all_configured_tools(): ).design all_tools = design.tool.get() - assert design.tune in all_tools - assert design.trm in all_tools - assert design.orbit in all_tools - assert design.orm in all_tools - assert design.chromaticity in all_tools - assert design.crm in all_tools - assert design.dispersion in all_tools - - -def test_tool_typed_properties_alias_existing_defaults(): - design = Accelerator.load( - "tests/config/EBSOrbit.yaml", - ignore_external=True, - include_locations=False, - ).design - - assert design.tool.tune is design.tune - assert design.tool.trm is design.trm - assert design.tool.orbit is design.orbit - assert design.tool.orm is design.orm - assert design.tool.chromaticity is design.chromaticity - assert design.tool.crm is design.crm - assert design.tool.dispersion is design.dispersion + assert design.tool.tune in all_tools + assert design.tool.trm in all_tools + assert design.tool.orbit in all_tools + assert design.tool.orm in all_tools + assert design.tool.chromaticity in all_tools + assert design.tool.crm in all_tools + assert design.tool.dispersion in all_tools def test_tool_raises_when_default_missing(ebs_lattice_file): @@ -117,7 +101,7 @@ def test_tool_raises_when_default_wrong_type(): ).design # Swap the registered default so it points to an object of the wrong type. - design._TOOLS["DEFAULT_TUNE_CORRECTION"] = design.orbit + design._TOOLS["DEFAULT_TUNE_CORRECTION"] = design.tool.orbit with pytest.raises(PyAMLException) as exc: _ = design.tool.tune diff --git a/tests/tuning_tools/test_tuning_dispersion.py b/tests/tuning_tools/test_tuning_dispersion.py index 9cd3e66b..a2abcc43 100644 --- a/tests/tuning_tools/test_tuning_dispersion.py +++ b/tests/tuning_tools/test_tuning_dispersion.py @@ -12,7 +12,7 @@ def test_tuning_orm(): sr = Accelerator.load(config_path) element_holder = sr.design - dispersion = element_holder.dispersion + dispersion = element_holder.tool.dispersion dispersion.measure() dispersion_data = dispersion.get() diff --git a/tests/tuning_tools/test_tuning_orbit_correction.py b/tests/tuning_tools/test_tuning_orbit_correction.py index 7cf8fb56..930d59e9 100644 --- a/tests/tuning_tools/test_tuning_orbit_correction.py +++ b/tests/tuning_tools/test_tuning_orbit_correction.py @@ -27,7 +27,7 @@ def test_tuning_orbit_correction(): x, y = bpms.positions.get().T # get reference orbit reference = np.concat((x, y)) # there should be nothing to correct, but still work - element_holder.orbit.correct(reference=reference) + element_holder.tool.orbit.correct(reference=reference) h_strengths = hcorr.strengths.get() + std_kick * np.random.normal(size=len(hcorr)) v_strengths = vcorr.strengths.get() + std_kick * np.random.normal(size=len(vcorr)) @@ -43,7 +43,7 @@ def test_tuning_orbit_correction(): assert np.isclose(std_bc[0], original_H, rtol=0, atol=1e-14) assert np.isclose(std_bc[1], original_V, rtol=0, atol=1e-14) - element_holder.orbit.correct(reference=None) + element_holder.tool.orbit.correct(reference=None) positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) @@ -53,7 +53,7 @@ def test_tuning_orbit_correction(): # mangle orbit again, test gain_h/gain_v hcorr.strengths.set(h_strengths) vcorr.strengths.set(v_strengths) - element_holder.orbit.correct(gain_h=0.5, gain_v=0.1) + element_holder.tool.orbit.correct(gain_h=0.5, gain_v=0.1) positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) @@ -63,7 +63,7 @@ def test_tuning_orbit_correction(): # mangle orbit again, test gain/gain_v hcorr.strengths.set(h_strengths) vcorr.strengths.set(v_strengths) - element_holder.orbit.correct(gain=1, gain_v=0.1) + element_holder.tool.orbit.correct(gain=1, gain_v=0.1) positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) @@ -73,7 +73,7 @@ def test_tuning_orbit_correction(): # mangle orbit again, test singular_values_h hcorr.strengths.set(h_strengths) vcorr.strengths.set(v_strengths) - element_holder.orbit.correct(singular_values_h=100) + element_holder.tool.orbit.correct(singular_values_h=100) positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) @@ -83,7 +83,7 @@ def test_tuning_orbit_correction(): # mangle orbit again, test singular_values_v hcorr.strengths.set(h_strengths) vcorr.strengths.set(v_strengths) - element_holder.orbit.correct(singular_values_v=50) + element_holder.tool.orbit.correct(singular_values_v=50) positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) @@ -93,7 +93,7 @@ def test_tuning_orbit_correction(): # mangle orbit again, test plane=H hcorr.strengths.set(h_strengths) vcorr.strengths.set(v_strengths) - element_holder.orbit.correct(plane="H") + element_holder.tool.orbit.correct(plane="H") positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) @@ -104,7 +104,7 @@ def test_tuning_orbit_correction(): # mangle orbit again, test plane=V hcorr.strengths.set(h_strengths) vcorr.strengths.set(v_strengths) - element_holder.orbit.correct(plane="V") + element_holder.tool.orbit.correct(plane="V") positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) @@ -116,65 +116,65 @@ def test_tuning_orbit_correction(): hcorr.strengths.set(h_strengths) vcorr.strengths.set(v_strengths) - element_holder.orbit.set_weight("SJ2A-C04-H", 2) - element_holder.orbit.correct() + element_holder.tool.orbit.set_weight("SJ2A-C04-H", 2) + element_holder.tool.orbit.correct() positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) assert np.isclose(std_ac[0], 5.057265926153962e-07, rtol=0, atol=1e-14) assert np.isclose(std_ac[1], 4.78927471675123e-07, rtol=0, atol=1e-14) - element_holder.orbit.set_weight("SJ2A-C04-H", 1) + element_holder.tool.orbit.set_weight("SJ2A-C04-H", 1) # mangle orbit again, test bpm weight hcorr.strengths.set(h_strengths) vcorr.strengths.set(v_strengths) - element_holder.orbit.set_weight("BPM_C04-05", 2) - element_holder.orbit.correct() + element_holder.tool.orbit.set_weight("BPM_C04-05", 2) + element_holder.tool.orbit.correct() positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) assert np.isclose(std_ac[0], 5.056719006414065e-07, rtol=0, atol=1e-14) assert np.isclose(std_ac[1], 4.790610352466602e-07, rtol=0, atol=1e-14) - element_holder.orbit.set_weight("BPM_C04-05", 1) + element_holder.tool.orbit.set_weight("BPM_C04-05", 1) # mangle orbit again, test virtual weight hcorr.strengths.set(h_strengths) vcorr.strengths.set(v_strengths) - element_holder.orbit.set_virtual_weight(2) - element_holder.orbit.correct() + element_holder.tool.orbit.set_virtual_weight(2) + element_holder.tool.orbit.correct() positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) assert np.isclose(std_ac[0], 5.05398857685373e-07, rtol=0, atol=1e-14) assert np.isclose(std_ac[1], 4.789270969888965e-07, rtol=0, atol=1e-14) - element_holder.orbit.set_virtual_weight(1000) + element_holder.tool.orbit.set_virtual_weight(1000) # mangle orbit again, test virtual target hcorr.strengths.set(h_strengths) vcorr.strengths.set(v_strengths) virtual_target = 1e-8 - element_holder.orbit.correct(virtual_target=virtual_target) + element_holder.tool.orbit.correct(virtual_target=virtual_target) delta_h_strengths = hcorr.strengths.get() - h_strengths delta_h_strengths_sum = -np.sum(delta_h_strengths) assert np.isclose(delta_h_strengths_sum - virtual_target, 0, rtol=0, atol=1e-13) - element_holder.orbit.set_virtual_weight(1) + element_holder.tool.orbit.set_virtual_weight(1) # mangle orbit again, test reference hcorr.strengths.set(h_strengths) vcorr.strengths.set(v_strengths) reference = np.concatenate((positions_bc[:, 0], positions_bc[:, 1])) - element_holder.orbit.correct(reference=reference) + element_holder.tool.orbit.correct(reference=reference) positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) assert np.isclose(std_ac[0], original_H, rtol=0, atol=1e-14) assert np.isclose(std_ac[1], original_V, rtol=0, atol=1e-14) # no need to mangle orbit again, test reference/plane=H - element_holder.orbit.correct(reference=reference / 2, plane="H") + element_holder.tool.orbit.correct(reference=reference / 2, plane="H") positions_ac = bpms.positions.get() std_ac = np.std(positions_ac, axis=0) assert np.isclose(std_ac[0], 3.360429728849497e-05, rtol=0, atol=1e-14) @@ -187,7 +187,7 @@ def test_tuning_orbit_correction(): frf = element_holder.rf.frequency.get() element_holder.rf.frequency.set(frf + 100) for _ in range(3): - element_holder.orbit.correct( + element_holder.tool.orbit.correct( reference=reference_before_rf, plane="H", rf=True, @@ -202,12 +202,12 @@ def test_tuning_orbit_correction(): x, y = bpms.positions.get().T # get reference orbit reference_before_rf = np.concat((x, y)) - rf_weight = element_holder.orbit.get_rf_weight() - element_holder.orbit.set_rf_weight(1.1 * rf_weight) + rf_weight = element_holder.tool.orbit.get_rf_weight() + element_holder.tool.orbit.set_rf_weight(1.1 * rf_weight) frf = element_holder.rf.frequency.get() element_holder.rf.frequency.set(frf + 100) for _ in range(8): - element_holder.orbit.correct( + element_holder.tool.orbit.correct( reference=reference_before_rf, plane="H", rf=True, @@ -217,12 +217,12 @@ def test_tuning_orbit_correction(): frf_after = element_holder.rf.frequency.get() assert np.isclose(frf, frf_after, rtol=0, atol=1e-16) - element_holder.orbit.set_rf_weight(rf_weight) + element_holder.tool.orbit.set_rf_weight(rf_weight) # test getters of weights - assert np.isclose(element_holder.orbit.get_weight("BPM_C04-05", plane="H"), 1, rtol=0, atol=1e-16) - assert np.isclose(element_holder.orbit.get_virtual_weight(), 1, rtol=0, atol=1e-16) - assert np.isclose(element_holder.orbit.get_rf_weight(), rf_weight, rtol=0, atol=1e-16) + assert np.isclose(element_holder.tool.orbit.get_weight("BPM_C04-05", plane="H"), 1, rtol=0, atol=1e-16) + assert np.isclose(element_holder.tool.orbit.get_virtual_weight(), 1, rtol=0, atol=1e-16) + assert np.isclose(element_holder.tool.orbit.get_rf_weight(), rf_weight, rtol=0, atol=1e-16) def test_tuning_orbit_correction_config(): diff --git a/tests/tuning_tools/test_tuning_orm.py b/tests/tuning_tools/test_tuning_orm.py index f3652f0c..3ea994f0 100644 --- a/tests/tuning_tools/test_tuning_orm.py +++ b/tests/tuning_tools/test_tuning_orm.py @@ -15,7 +15,7 @@ def test_tuning_orm(): sr = Accelerator.load(config_path) element_holder = sr.design - orm = element_holder.orm + orm = element_holder.tool.orm bpms = element_holder.diagnostic.bpms.get("BPM") hcorr_names = element_holder.magnets.get("HCorr").names()[:4] @@ -31,4 +31,4 @@ def test_tuning_orm(): orm.save(save_path=f.name) # load saved file - element_holder.orbit.load(f.name) + element_holder.tool.orbit.load(f.name) diff --git a/tests/tuning_tools/test_tuning_tools.py b/tests/tuning_tools/test_tuning_tools.py index 5c748b00..635142a2 100644 --- a/tests/tuning_tools/test_tuning_tools.py +++ b/tests/tuning_tools/test_tuning_tools.py @@ -12,11 +12,11 @@ def callback(action: Action, data: dict): def test_tune_tool(): sr = Accelerator.load("tests/config/EBSTune.yaml", include_locations=False) sr.design.get_lattice().disable_6d() - sr.design.trm.measure(callback=callback) - sr.design.trm.save("tunemat.json") - sr.design.tune.load("tunemat.json") - sr.design.tune.set([0.17, 0.32], iter=2) - tune = sr.design.tune.readback() + sr.design.tool.trm.measure(callback=callback) + sr.design.tool.trm.save("tunemat.json") + sr.design.tool.tune.load("tunemat.json") + sr.design.tool.tune.set([0.17, 0.32], iter=2) + tune = sr.design.tool.tune.readback() assert np.abs(tune[0] - 0.17) < 1e-5 assert np.abs(tune[1] - 0.32) < 1e-5 @@ -24,20 +24,20 @@ def test_tune_tool(): def test_tune_add(): sr = Accelerator.load("tests/config/EBSTune.yaml", include_locations=False) sr.design.get_lattice().disable_6d() - sr.design.tune.load("tunemat.json") - tune_initial = sr.design.tune.readback() + sr.design.tool.tune.load("tunemat.json") + tune_initial = sr.design.tool.tune.readback() dtune = np.array([0.01, -0.01]) - sr.design.tune.add(dtune) - tune = sr.design.tune.readback() + sr.design.tool.tune.add(dtune) + tune = sr.design.tool.tune.readback() np.testing.assert_allclose(tune - tune_initial, dtune, atol=1e-5) def test_chroma_tool(): sr: Accelerator = Accelerator.load("tests/config/EBSOrbit.yaml", include_locations=False) sr.design.get_lattice().enable_6d() - sr.design.chromaticity.set([8.0, 5.0], iter=2) + sr.design.tool.chromaticity.set([8.0, 5.0], iter=2) QpAT = sr.design.get_lattice().get_chrom()[:-1] - Qp = sr.design.chromaticity.readback() + Qp = sr.design.tool.chromaticity.readback() assert np.abs(Qp[0] - 8.0) < 1e-3 assert np.abs(Qp[1] - 5.0) < 1e-3 assert np.abs(QpAT[0] - 8.0) < 1e-2 @@ -48,7 +48,7 @@ def test_chroma_add(): sr: Accelerator = Accelerator.load("tests/config/EBSOrbit.yaml", include_locations=False) sr.design.get_lattice().enable_6d() chromaAT = sr.design.get_lattice().get_chrom()[:-1] - sr.design.chromaticity.add([0.5, 0.4]) + sr.design.tool.chromaticity.add([0.5, 0.4]) chromaAT2 = sr.design.get_lattice().get_chrom()[:-1] chromaDiff = chromaAT2 - chromaAT assert np.abs(chromaDiff[0] - 0.5) < 5e-2 From 24d97439c18a981449db4c6d13c0f0bca9295c0b Mon Sep 17 00:00:00 2001 From: kparasch Date: Mon, 28 Sep 2026 17:27:21 +0200 Subject: [PATCH 26/49] template variation 2 --- examples/template/build_template_example.py | 5 +++ examples/template/templated_config.yaml | 29 +++++++++++++ pyaml/accelerator.py | 4 ++ pyaml/configuration/fileloader.py | 43 +++++++++++++++++++ pyaml/configuration/template.py | 47 +++++++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 examples/template/build_template_example.py create mode 100644 examples/template/templated_config.yaml create mode 100644 pyaml/configuration/template.py diff --git a/examples/template/build_template_example.py b/examples/template/build_template_example.py new file mode 100644 index 00000000..b863f500 --- /dev/null +++ b/examples/template/build_template_example.py @@ -0,0 +1,5 @@ +from pyaml.accelerator import Accelerator + +sr = Accelerator.load("templated_config.yaml") +for dev in sr._devices: + print(dev) diff --git a/examples/template/templated_config.yaml b/examples/template/templated_config.yaml new file mode 100644 index 00000000..71be2cc3 --- /dev/null +++ b/examples/template/templated_config.yaml @@ -0,0 +1,29 @@ +class: pyaml.accelerator.Accelerator +machine: sr +facility: PETRAIII +energy: 6e9 +controls: + - class: tango.pyaml.controlsystem.TangoControlSystem + tango_host: ebs-simu-3:10000 + name: live + catalog: + class: tango.pyaml.tango_catalog.TangoCatalog + disconnected: True +devices: +- ${template:SHI,SHI,01} +- ${template:SHI,SHI,02} +- ${template:SHI,SHI,03} +templates: + - name: SHI + parameters: + - sh_name + - ii + config: + class: pyaml.magnet.hcorrector.HCorrector + name: "{sh_name}-{ii}" + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + unit: rad + hardware_unit: str + calibration_factor: 1.0 + powerconverter: tango/MAGNET/{sh_name}-{ii}/B1L diff --git a/pyaml/accelerator.py b/pyaml/accelerator.py index 0bc27301..58e4da5b 100644 --- a/pyaml/accelerator.py +++ b/pyaml/accelerator.py @@ -392,6 +392,10 @@ def load(filename: str, include_locations: bool = False, ignore_external=False, """ manager = ConfigurationManager() + # ensure TemplateManager is clean before loading a new accelerator config + from .configuration.template import TemplateManager + + TemplateManager.clear() if not validate and include_locations: warnings.warn( diff --git a/pyaml/configuration/fileloader.py b/pyaml/configuration/fileloader.py index ef5f9f0e..05261258 100644 --- a/pyaml/configuration/fileloader.py +++ b/pyaml/configuration/fileloader.py @@ -293,6 +293,39 @@ def resolve_file(value: str, context: LoadContext | None = None) -> Any: return _load(value, context) +@resolver("template") +def resolve_template(value: str, _context: LoadContext | None = None) -> Any: + """ + Resolve a configuration by template. + + Parameters + ---------- + value : str + Name and arguments of the template. + Must be in the format: NAME,ARG1,ARG2,... + _context : LoadContext or None, optional + Unused loading context retained for resolver compatibility. + + Returns + ------- + object + Parsed and expanded configuration data. + + Raises + ------ + PyAMLException + If the environment variable is not set. + """ + try: + from .template import TemplateManager + + name, args = value.split(",", maxsplit=1) + arguments = args.split(",") + return TemplateManager.generate(name, *arguments) + except KeyError as exc: + raise PyAMLException(f"Invalid template resolver call {value}.") from exc + + def load(filename: str, include_locations: bool = False) -> Union[dict, list]: """ Load a configuration file. @@ -363,6 +396,16 @@ def expand(self, obj: Union[dict, list, Any]) -> Union[dict, list, Any]: """ if isinstance(obj, dict): + if "templates" in obj: + from .template import TemplateManager + + if "templates" in obj: + templates = obj.pop("templates") + for template in templates: + TemplateManager.add( + name=template["name"], parameters=template["parameters"], config=template["config"] + ) + return self._expand_dict(obj) if isinstance(obj, list): return self._expand_list(obj) diff --git a/pyaml/configuration/template.py b/pyaml/configuration/template.py new file mode 100644 index 00000000..1ca29aa3 --- /dev/null +++ b/pyaml/configuration/template.py @@ -0,0 +1,47 @@ +import yaml + +from ..common.element import Element +from ..common.exception import PyAMLConfigException + + +class TemplateManager: + template_names = [] + template_parameters = {} + template_codes = {} + + @classmethod + def add(cls, name: str, parameters: list[str], config: dict): + if name in cls.template_names: + raise PyAMLConfigException("Template '{name}' has already been registered.") + + cls.template_names.append(name) + cls.template_parameters[name] = parameters + cls.template_codes[name] = yaml.safe_dump(config, sort_keys=False) # serialize into yaml, retain order + + @classmethod + def generate(cls, name: str, *args): + number_of_parameters = len(cls.template_parameters[name]) + number_of_arguments = len(args) + + if number_of_parameters != number_of_arguments: + raise PyAMLConfigException( + f"Invalid number of arguments ({args}: {number_of_arguments}) passed to template {name}." + f" Expected {number_of_parameters}." + ) + + # name the arguments by position + arguments_dict = {} + for arg_name, arg_value in zip(cls.template_parameters[name], args, strict=True): + arguments_dict[arg_name] = arg_value + + # use standard str function format to replace. + new_code = cls.template_codes[name].format(**arguments_dict) + # code str must already be in yaml format + config = yaml.safe_load(new_code) + return config + + @classmethod + def clear(cls): + cls.template_names = [] + cls.template_parameters = {} + cls.template_codes = {} From 2144ec4d9b81a2551a70ea95841da2278b830e1c Mon Sep 17 00:00:00 2001 From: kparasch Date: Wed, 30 Sep 2026 14:06:02 +0200 Subject: [PATCH 27/49] no longer serializing into a dictionary. Instead walking through dictionary recursively and replacing text to avoid edge cases which produce invalid yaml or json strings. Also raise a warning if an argument containts {...} text --- pyaml/configuration/template.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/pyaml/configuration/template.py b/pyaml/configuration/template.py index 1ca29aa3..fc98093f 100644 --- a/pyaml/configuration/template.py +++ b/pyaml/configuration/template.py @@ -1,8 +1,24 @@ -import yaml +import copy +import logging +import re from ..common.element import Element from ..common.exception import PyAMLConfigException +logger = logging.getLogger(__name__) + + +def substitute(obj, arguments): + if isinstance(obj, dict): + return {key: substitute(value, arguments) for key, value in obj.items()} + if isinstance(obj, list): + return [substitute(value, arguments) for value in obj] + if isinstance(obj, str): + for name, value in arguments.items(): + obj = obj.replace("{" + name + "}", str(value)) + return obj + return obj + class TemplateManager: template_names = [] @@ -16,7 +32,7 @@ def add(cls, name: str, parameters: list[str], config: dict): cls.template_names.append(name) cls.template_parameters[name] = parameters - cls.template_codes[name] = yaml.safe_dump(config, sort_keys=False) # serialize into yaml, retain order + cls.template_codes[name] = config @classmethod def generate(cls, name: str, *args): @@ -32,12 +48,16 @@ def generate(cls, name: str, *args): # name the arguments by position arguments_dict = {} for arg_name, arg_value in zip(cls.template_parameters[name], args, strict=True): + # check if {...} is included in any of the arguments, and a raise a warning if so. + if re.search(r"\{[^{}]+\}", str(arg_value)): + logger.warning( + f"Argument {arg_name!r} for template {name!r} contains a placeholder: {arg_value!r}. " + "Sequential replacement may substitute placeholders inside this argument.", + ) arguments_dict[arg_name] = arg_value - # use standard str function format to replace. - new_code = cls.template_codes[name].format(**arguments_dict) - # code str must already be in yaml format - config = yaml.safe_load(new_code) + config = substitute(copy.deepcopy(cls.template_codes[name]), arguments_dict) + return config @classmethod From e9d1cf5b5223afc2a3878b8f772933030ac8c678 Mon Sep 17 00:00:00 2001 From: kparasch Date: Wed, 30 Sep 2026 15:01:06 +0200 Subject: [PATCH 28/49] Cleaner way of registering templates --- pyaml/configuration/fileloader.py | 40 ++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/pyaml/configuration/fileloader.py b/pyaml/configuration/fileloader.py index 05261258..9e7b1965 100644 --- a/pyaml/configuration/fileloader.py +++ b/pyaml/configuration/fileloader.py @@ -15,7 +15,7 @@ from contextlib import contextmanager from dataclasses import dataclass, field from pathlib import Path -from typing import Any, Union +from typing import Any, Optional, Union import yaml from yaml import CLoader @@ -23,6 +23,7 @@ from yaml.loader import SafeLoader from .. import PyAMLException +from .template import TemplateManager logger = logging.getLogger(__name__) @@ -147,6 +148,8 @@ class LoadContext: include_locations: bool = False include_stack: list[Path] = field(default_factory=list) + expand: Optional[Callable] = None + # Is populated with ConfigLoader's expand function for use in template resolver @contextmanager def loading(self, path: Path): @@ -321,7 +324,8 @@ def resolve_template(value: str, _context: LoadContext | None = None) -> Any: name, args = value.split(",", maxsplit=1) arguments = args.split(",") - return TemplateManager.generate(name, *arguments) + generated = TemplateManager.generate(name, *arguments) + return _context.expand(generated) except KeyError as exc: raise PyAMLException(f"Invalid template resolver call {value}.") from exc @@ -353,6 +357,7 @@ def _load(filename: str, context: LoadContext) -> Union[dict, list]: else: raise PyAMLException(f"{filename} File format not supported (only .yaml .yml or .json)") + context.expand = loader.expand return loader.load() @@ -396,16 +401,6 @@ def expand(self, obj: Union[dict, list, Any]) -> Union[dict, list, Any]: """ if isinstance(obj, dict): - if "templates" in obj: - from .template import TemplateManager - - if "templates" in obj: - templates = obj.pop("templates") - for template in templates: - TemplateManager.add( - name=template["name"], parameters=template["parameters"], config=template["config"] - ) - return self._expand_dict(obj) if isinstance(obj, list): return self._expand_list(obj) @@ -558,6 +553,17 @@ def _expand_list(self, items: list) -> list: return expanded + def register_templates(self, config): + if not isinstance(config, dict): + return + + for template in config.pop("templates", []): + TemplateManager.add( + name=template["name"], + parameters=template["parameters"], + config=template["config"], + ) + @abstractmethod def load(self) -> Union[dict, list]: """Load and parse the current configuration file.""" @@ -593,7 +599,10 @@ def load(self) -> Union[dict, list]: logger.log(logging.DEBUG, f"Loading YAML file '{self.path}'") with open(self.path) as file: try: - return self.expand(yaml.load(file, Loader=self._loader)) + parsed_config = yaml.load(file, Loader=self._loader) + # "templates" is popped out here if it exists + self.register_templates(parsed_config) + return self.expand(parsed_config) except yaml.YAMLError as exc: raise PyAMLException(f"{self.path}: {exc}") from exc @@ -626,7 +635,10 @@ def load(self) -> Union[dict, list]: logger.log(logging.DEBUG, f"Loading JSON file '{self.path}'") with open(self.path) as file: try: - return self.expand(json.load(file)) + parsed_config = json.load(file) + # "templates" is popped out here if it exists + self.register_templates(parsed_config) + return self.expand(parsed_config) except json.JSONDecodeError as exc: raise PyAMLException(f"{self.path}: {exc}") from exc From 26669b9dbfacca22c0f45056797b12d923d79623 Mon Sep 17 00:00:00 2001 From: kparasch Date: Wed, 30 Sep 2026 15:04:57 +0200 Subject: [PATCH 29/49] Catch RecursionError in case it is caused by excessive recursive template usage. --- pyaml/configuration/fileloader.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pyaml/configuration/fileloader.py b/pyaml/configuration/fileloader.py index 9e7b1965..cd8b417c 100644 --- a/pyaml/configuration/fileloader.py +++ b/pyaml/configuration/fileloader.py @@ -297,7 +297,7 @@ def resolve_file(value: str, context: LoadContext | None = None) -> Any: @resolver("template") -def resolve_template(value: str, _context: LoadContext | None = None) -> Any: +def resolve_template(value: str, context: LoadContext | None = None) -> Any: """ Resolve a configuration by template. @@ -324,8 +324,14 @@ def resolve_template(value: str, _context: LoadContext | None = None) -> Any: name, args = value.split(",", maxsplit=1) arguments = args.split(",") - generated = TemplateManager.generate(name, *arguments) - return _context.expand(generated) + try: + generated = TemplateManager.generate(name, *arguments) + return context.expand(generated) + except RecursionError as exc: + raise PyAMLException( + f"Recursion limit reached while expanding template {name!r}. " + "Check for circular template references or excessive nesting." + ) from exc except KeyError as exc: raise PyAMLException(f"Invalid template resolver call {value}.") from exc From 0f7c1805e55bd25a8c51032404074d054feeb89d Mon Sep 17 00:00:00 2001 From: kparasch Date: Wed, 30 Sep 2026 15:31:01 +0200 Subject: [PATCH 30/49] Change to instance-based template managers that are attached to the configuration manager. --- pyaml/accelerator.py | 4 ---- pyaml/configuration/fileloader.py | 38 +++++++++++++++++++++---------- pyaml/configuration/manager.py | 10 +++++--- pyaml/configuration/template.py | 38 ++++++++++++++----------------- 4 files changed, 50 insertions(+), 40 deletions(-) diff --git a/pyaml/accelerator.py b/pyaml/accelerator.py index 58e4da5b..0bc27301 100644 --- a/pyaml/accelerator.py +++ b/pyaml/accelerator.py @@ -392,10 +392,6 @@ def load(filename: str, include_locations: bool = False, ignore_external=False, """ manager = ConfigurationManager() - # ensure TemplateManager is clean before loading a new accelerator config - from .configuration.template import TemplateManager - - TemplateManager.clear() if not validate and include_locations: warnings.warn( diff --git a/pyaml/configuration/fileloader.py b/pyaml/configuration/fileloader.py index cd8b417c..a79a9415 100644 --- a/pyaml/configuration/fileloader.py +++ b/pyaml/configuration/fileloader.py @@ -15,7 +15,7 @@ from contextlib import contextmanager from dataclasses import dataclass, field from pathlib import Path -from typing import Any, Optional, Union +from typing import Any, Union import yaml from yaml import CLoader @@ -139,6 +139,8 @@ class LoadContext: Preserve source locations in loaded mappings. include_stack : list[pathlib.Path], optional Active include chain. Usually left empty for a new session. + templates : TemplateManager, optional + Template registry shared by files in this session. Defaults to a fresh registry. Methods ------- @@ -148,8 +150,9 @@ class LoadContext: include_locations: bool = False include_stack: list[Path] = field(default_factory=list) - expand: Optional[Callable] = None + expand: Callable[[Any], Any] | None = None # Is populated with ConfigLoader's expand function for use in template resolver + templates: TemplateManager = field(default_factory=TemplateManager) @contextmanager def loading(self, path: Path): @@ -306,8 +309,8 @@ def resolve_template(value: str, context: LoadContext | None = None) -> Any: value : str Name and arguments of the template. Must be in the format: NAME,ARG1,ARG2,... - _context : LoadContext or None, optional - Unused loading context retained for resolver compatibility. + context : LoadContext or None, optional + Active loading context providing the template registry and expansion callback. Returns ------- @@ -317,15 +320,16 @@ def resolve_template(value: str, context: LoadContext | None = None) -> Any: Raises ------ PyAMLException - If the environment variable is not set. + If the template call is invalid or expansion exceeds the recursion limit. """ - try: - from .template import TemplateManager + if context is None or context.expand is None: + raise PyAMLException("Template resolver requires an active loading context.") + try: name, args = value.split(",", maxsplit=1) arguments = args.split(",") try: - generated = TemplateManager.generate(name, *arguments) + generated = context.templates.generate(name, *arguments) return context.expand(generated) except RecursionError as exc: raise PyAMLException( @@ -336,16 +340,22 @@ def resolve_template(value: str, context: LoadContext | None = None) -> Any: raise PyAMLException(f"Invalid template resolver call {value}.") from exc -def load(filename: str, include_locations: bool = False) -> Union[dict, list]: +def load(filename: str, include_locations: bool = False, *, templates: TemplateManager | None = None) -> Union[dict, list]: """ Load a configuration file. When include_locations is False, uses the faster C-based YAML loader and skips including source location metadata. + + ``templates`` may be supplied to share definitions across configuration + fragments. When omitted, this load gets its own registry. """ # Create a new context - context = LoadContext(include_locations=include_locations) + context = LoadContext( + include_locations=include_locations, + templates=templates if templates is not None else TemplateManager(), + ) return _load(filename, context) @@ -363,8 +373,12 @@ def _load(filename: str, context: LoadContext) -> Union[dict, list]: else: raise PyAMLException(f"{filename} File format not supported (only .yaml .yml or .json)") + previous_expand = context.expand context.expand = loader.expand - return loader.load() + try: + return loader.load() + finally: + context.expand = previous_expand def _is_supported_file(value: Any) -> bool: @@ -564,7 +578,7 @@ def register_templates(self, config): return for template in config.pop("templates", []): - TemplateManager.add( + self.context.templates.add( name=template["name"], parameters=template["parameters"], config=template["config"], diff --git a/pyaml/configuration/manager.py b/pyaml/configuration/manager.py index 68baf41c..b7626033 100644 --- a/pyaml/configuration/manager.py +++ b/pyaml/configuration/manager.py @@ -24,6 +24,7 @@ from ..common.exception import PyAMLConfigException from .fileloader import ROOT, load from .restfetcher import REMOTE_BASE_URL_KEY, SourceRoot, fetch_remote_config, is_remote_url, resolve_reference +from .template import TemplateManager _INTERNAL_METADATA_KEYS = {"__location__", "__fieldlocations__", REMOTE_BASE_URL_KEY} _VALID_QUERY_KEY_RE = re.compile(r"^[A-Z0-9_]+$") @@ -59,7 +60,7 @@ class ConfigurationManager: replace(category, element) Replace an existing named entry in an aggregated category. clear(category=None) - Clear the aggregated state, or a single root field/category. + Clear the aggregated state and templates, or a single root field/category. categories() Return categories that currently contain entries. keys(category=None) @@ -156,7 +157,8 @@ def __init__(self): Initialize an empty configuration manager. The manager starts with the default accelerator type and empty named - categories. Source tracking is enabled as fragments are added. + categories. Source tracking is enabled as fragments are added. Local + file fragments share this manager's private template registry. """ self._state: dict[str, Any] = {"class_path": self.DEFAULT_CLASS_PATH} self._items_by_category: dict[str, dict[str, dict[str, Any]]] = {category: {} for category in self.NAMED_CATEGORIES} @@ -164,6 +166,7 @@ def __init__(self): self._field_sources: dict[str, str] = {} self._build_root: SourceRoot = ROOT.get() self._build_root_locked = False + self._templates = TemplateManager() def add(self, payload, **kwargs) -> None: r""" @@ -306,6 +309,7 @@ def clear(self, category: str | None = None) -> None: >>> manager.clear() """ if category is None: + self._templates.clear() self._state = {"class_path": self.DEFAULT_CLASS_PATH} self._field_sources.clear() for name in self.NAMED_CATEGORIES: @@ -698,7 +702,7 @@ def _load_payload( source_root = resolved_path.parent try: ROOT.set(source_root) - fragment = load(resolved_path.name, include_locations) + fragment = load(resolved_path.name, include_locations, templates=self._templates) finally: ROOT.set(previous_root) diff --git a/pyaml/configuration/template.py b/pyaml/configuration/template.py index fc98093f..e5e01813 100644 --- a/pyaml/configuration/template.py +++ b/pyaml/configuration/template.py @@ -2,7 +2,6 @@ import logging import re -from ..common.element import Element from ..common.exception import PyAMLConfigException logger = logging.getLogger(__name__) @@ -21,22 +20,21 @@ def substitute(obj, arguments): class TemplateManager: - template_names = [] - template_parameters = {} - template_codes = {} + """Store template definitions for one configuration manager or loading session.""" - @classmethod - def add(cls, name: str, parameters: list[str], config: dict): - if name in cls.template_names: - raise PyAMLConfigException("Template '{name}' has already been registered.") + def __init__(self): + self.template_parameters: dict[str, list[str]] = {} + self.template_codes: dict[str, dict] = {} - cls.template_names.append(name) - cls.template_parameters[name] = parameters - cls.template_codes[name] = config + def add(self, name: str, parameters: list[str], config: dict): + if name in self.template_codes: + raise PyAMLConfigException(f"Template '{name}' has already been registered.") - @classmethod - def generate(cls, name: str, *args): - number_of_parameters = len(cls.template_parameters[name]) + self.template_parameters[name] = list(parameters) + self.template_codes[name] = copy.deepcopy(config) + + def generate(self, name: str, *args): + number_of_parameters = len(self.template_parameters[name]) number_of_arguments = len(args) if number_of_parameters != number_of_arguments: @@ -47,7 +45,7 @@ def generate(cls, name: str, *args): # name the arguments by position arguments_dict = {} - for arg_name, arg_value in zip(cls.template_parameters[name], args, strict=True): + for arg_name, arg_value in zip(self.template_parameters[name], args, strict=True): # check if {...} is included in any of the arguments, and a raise a warning if so. if re.search(r"\{[^{}]+\}", str(arg_value)): logger.warning( @@ -56,12 +54,10 @@ def generate(cls, name: str, *args): ) arguments_dict[arg_name] = arg_value - config = substitute(copy.deepcopy(cls.template_codes[name]), arguments_dict) + config = substitute(copy.deepcopy(self.template_codes[name]), arguments_dict) return config - @classmethod - def clear(cls): - cls.template_names = [] - cls.template_parameters = {} - cls.template_codes = {} + def clear(self): + self.template_parameters.clear() + self.template_codes.clear() From 957d2c2ffa6665773f1a24042d318d3dcb663b8b Mon Sep 17 00:00:00 2001 From: kparasch Date: Wed, 30 Sep 2026 15:41:49 +0200 Subject: [PATCH 31/49] add tests for templates --- tests/configuration/test_templates.py | 211 ++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 tests/configuration/test_templates.py diff --git a/tests/configuration/test_templates.py b/tests/configuration/test_templates.py new file mode 100644 index 00000000..acbf4714 --- /dev/null +++ b/tests/configuration/test_templates.py @@ -0,0 +1,211 @@ +import json + +import pytest +import yaml + +from pyaml import PyAMLException +from pyaml.accelerator import Accelerator +from pyaml.common.exception import PyAMLConfigException +from pyaml.configuration import ConfigurationManager +from pyaml.configuration.fileloader import FIELD_LOCATIONS_KEY, ROOT, load +from pyaml.configuration.template import TemplateManager + + +def _template_definition(prefix=""): + return { + "name": "device", + "parameters": ["name"], + "config": {"class": "pyaml.magnet.hcorrector.HCorrector", "name": prefix + "{name}"}, + } + + +def _write_config(path, config): + path.write_text(json.dumps(config) if path.suffix == ".json" else yaml.safe_dump(config, sort_keys=False)) + return path + + +@pytest.fixture +def template_config_root(tmp_path): + previous_root = ROOT.get() + ROOT.set(tmp_path) + try: + yield tmp_path + finally: + ROOT.set(previous_root) + + +def test_template_definitions_are_independent_of_inputs_and_results(): + registry = TemplateManager() + parameters = ["name"] + config = {"name": "{name}", "nested": [1]} + registry.add("T", parameters, config) + + parameters.clear() + config["nested"].append(2) + + generated = registry.generate("T", "Q1") + assert generated == {"name": "Q1", "nested": [1]} + + generated["nested"].append(3) + + assert registry.generate("T", "Q2") == {"name": "Q2", "nested": [1]} + + +def test_template_registry_rejects_duplicate_names(): + registry = TemplateManager() + registry.add("T", ["name"], {"name": "{name}"}) + + with pytest.raises(PyAMLConfigException, match="Template 'T'.*registered"): + registry.add("T", [], {}) + + +@pytest.mark.parametrize("suffix", [".yaml", ".json"]) +def test_standalone_loads_have_fresh_template_registries(tmp_path, suffix): + path = _write_config( + tmp_path / ("config" + suffix), + {"templates": [_template_definition()], "devices": ["${template:device,Q1}"]}, + ) + + first = load(str(path)) + second = load(str(path)) + + assert first["devices"][0]["name"] == "Q1" + assert second["devices"][0]["name"] == "Q1" + + use_only = _write_config(tmp_path / "use.yaml", {"devices": ["${template:device,Q1}"]}) + with pytest.raises(PyAMLException, match="Invalid template resolver call"): + load(str(use_only)) + + +def test_configuration_managers_have_independent_templates(tmp_path): + first_definitions = _write_config(tmp_path / "first.yaml", {"templates": [_template_definition("first-")]}) + second_definitions = _write_config(tmp_path / "second.json", {"templates": [_template_definition("second-")]}) + usage = _write_config(tmp_path / "usage.yaml", {"devices": ["${template:device,Q1}"]}) + first = ConfigurationManager() + second = ConfigurationManager() + + first.add(first_definitions) + second.add(second_definitions) + first.add(usage) + second.add(usage) + + assert first.keys("devices") == ["first-Q1"] + assert second.keys("devices") == ["second-Q1"] + + first.clear() + second.clear("devices") + second.add(usage) + + assert second.keys("devices") == ["second-Q1"] + + +def test_manager_category_clear_preserves_templates(tmp_path): + definitions = _write_config(tmp_path / "definitions.yaml", {"templates": [_template_definition()]}) + usage = _write_config(tmp_path / "usage.yaml", {"devices": ["${template:device,Q1}"]}) + manager = ConfigurationManager() + manager.add(definitions) + manager.add(usage) + + manager.clear("devices") + manager.add(usage) + + assert manager.keys("devices") == ["Q1"] + + +def test_manager_clear_resets_template_registration(tmp_path): + definitions = _write_config(tmp_path / "definitions.yaml", {"templates": [_template_definition()]}) + usage = _write_config(tmp_path / "usage.yaml", {"devices": ["${template:device,Q1}"]}) + manager = ConfigurationManager() + manager.add(definitions) + + manager.clear() + + with pytest.raises(PyAMLException, match="Invalid template resolver call"): + manager.add(usage) + + manager.add(definitions) + manager.add(usage) + + assert manager.keys("devices") == ["Q1"] + + +def test_included_files_and_nested_templates_share_registry(template_config_root, monkeypatch): + tmp_path = template_config_root + monkeypatch.setenv("PYAML_TEST_HOST", "localhost") + _write_config(tmp_path / "definitions.yaml", {"templates": [_template_definition()]}) + _write_config(tmp_path / "Q1.json", {"factor": 1.5}) + path = _write_config( + tmp_path / "parent.yaml", + { + "templates": [ + { + "name": "wrapper", + "parameters": ["name"], + "config": { + "device": "${template:device,{name}}", + "model": "{name}.json", + "host": "${env:PYAML_TEST_HOST}", + }, + } + ], + "definitions": "definitions.yaml", + "result": "${template:wrapper,Q1}", + }, + ) + + loaded = load(str(path), include_locations=True) + result = loaded["result"] + + assert result["device"]["name"] == "Q1" + assert result["model"] == {"factor": 1.5} + assert result["host"] == "localhost" + assert "templates" in loaded[FIELD_LOCATIONS_KEY] + assert "name" in result["device"][FIELD_LOCATIONS_KEY] + + +def test_explicit_registry_can_be_shared_between_standalone_loads(tmp_path): + registry = TemplateManager() + definitions = _write_config(tmp_path / "definitions.yaml", {"templates": [_template_definition()]}) + usage = _write_config(tmp_path / "usage.json", {"devices": ["${template:device,Q1}"]}) + + load(str(definitions), templates=registry) + result = load(str(usage), templates=registry) + + assert result["devices"][0]["name"] == "Q1" + + +def test_recursive_template_still_reports_configuration_error(tmp_path): + path = _write_config( + tmp_path / "recursive.yaml", + { + "templates": [{"name": "T", "parameters": ["name"], "config": {"child": "${template:T,{name}}"}}], + "result": "${template:T,Q1}", + }, + ) + + with pytest.raises(PyAMLException, match="Recursion limit reached while expanding template"): + load(str(path)) + + +def test_accelerator_load_does_not_clear_an_existing_managers_templates(template_config_root): + tmp_path = template_config_root + definitions = _write_config(tmp_path / "definitions.yaml", {"templates": [_template_definition()]}) + manager = ConfigurationManager() + manager.add(definitions) + accelerator = _write_config( + tmp_path / "accelerator.yaml", + { + "class": "pyaml.accelerator.Accelerator", + "facility": "test", + "machine": "sr", + "energy": 3e9, + "templates": [_template_definition("other-")], + }, + ) + usage = _write_config(tmp_path / "usage.yaml", {"devices": ["${template:device,Q1}"]}) + + for _ in range(2): + Accelerator.load(str(accelerator)) + manager.add(usage) + + assert manager.keys("devices") == ["Q1"] From da311724ec615c3c9ed224a2635aa7bc3f7daaec Mon Sep 17 00:00:00 2001 From: kparasch Date: Wed, 30 Sep 2026 16:15:03 +0200 Subject: [PATCH 32/49] Allow templates with zero arguments. --- pyaml/configuration/fileloader.py | 6 +++--- tests/configuration/test_templates.py | 29 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/pyaml/configuration/fileloader.py b/pyaml/configuration/fileloader.py index a79a9415..ff96b43e 100644 --- a/pyaml/configuration/fileloader.py +++ b/pyaml/configuration/fileloader.py @@ -308,7 +308,7 @@ def resolve_template(value: str, context: LoadContext | None = None) -> Any: ---------- value : str Name and arguments of the template. - Must be in the format: NAME,ARG1,ARG2,... + Must be in the format: NAME,ARG1,ARG2,... or NAME for no arguments. context : LoadContext or None, optional Active loading context providing the template registry and expansion callback. @@ -326,8 +326,8 @@ def resolve_template(value: str, context: LoadContext | None = None) -> Any: raise PyAMLException("Template resolver requires an active loading context.") try: - name, args = value.split(",", maxsplit=1) - arguments = args.split(",") + name, separator, argument_text = value.partition(",") + arguments = argument_text.split(",") if separator else [] try: generated = context.templates.generate(name, *arguments) return context.expand(generated) diff --git a/tests/configuration/test_templates.py b/tests/configuration/test_templates.py index acbf4714..712040b7 100644 --- a/tests/configuration/test_templates.py +++ b/tests/configuration/test_templates.py @@ -174,6 +174,35 @@ def test_explicit_registry_can_be_shared_between_standalone_loads(tmp_path): assert result["devices"][0]["name"] == "Q1" +def test_template_calls_distinguish_no_arguments_from_an_empty_argument(tmp_path): + path = _write_config( + tmp_path / "arguments.yaml", + { + "templates": [ + {"name": "constant", "parameters": [], "config": {"name": "Q1"}}, + _template_definition(), + ], + "constant": "${template:constant}", + "empty": "${template:device,}", + }, + ) + + result = load(str(path)) + + assert result["constant"]["name"] == "Q1" + assert result["empty"]["name"] == "" + + +def test_template_call_without_required_arguments_reports_count_error(tmp_path): + path = _write_config( + tmp_path / "missing_argument.yaml", + {"templates": [_template_definition()], "result": "${template:device}"}, + ) + + with pytest.raises(PyAMLConfigException, match=r"Invalid number of arguments.*Expected 1"): + load(str(path)) + + def test_recursive_template_still_reports_configuration_error(tmp_path): path = _write_config( tmp_path / "recursive.yaml", From d4721f7fa29eac4ef2c094c3916f50d958b1f34d Mon Sep 17 00:00:00 2001 From: kparasch Date: Wed, 30 Sep 2026 16:46:18 +0200 Subject: [PATCH 33/49] docstring improvement --- examples/template/build_template_example.py | 2 + pyaml/configuration/fileloader.py | 78 +++++++++++++---- pyaml/configuration/manager.py | 6 +- pyaml/configuration/template.py | 93 ++++++++++++++++++++- 4 files changed, 160 insertions(+), 19 deletions(-) diff --git a/examples/template/build_template_example.py b/examples/template/build_template_example.py index b863f500..7572cb83 100644 --- a/examples/template/build_template_example.py +++ b/examples/template/build_template_example.py @@ -1,3 +1,5 @@ +"""Load and print templated devices; run from this example's directory.""" + from pyaml.accelerator import Accelerator sr = Accelerator.load("templated_config.yaml") diff --git a/pyaml/configuration/fileloader.py b/pyaml/configuration/fileloader.py index ff96b43e..75602e77 100644 --- a/pyaml/configuration/fileloader.py +++ b/pyaml/configuration/fileloader.py @@ -1,8 +1,9 @@ """ Load PyAML configuration files and expand nested references. -The loader supports YAML and JSON files, environment and path resolvers, -recursive file includes, and optional source-location metadata for diagnostics. +The loader supports YAML and JSON files, parameterized templates, environment +and path resolvers, recursive file includes, and optional source-location +metadata for diagnostics. """ import io @@ -139,6 +140,9 @@ class LoadContext: Preserve source locations in loaded mappings. include_stack : list[pathlib.Path], optional Active include chain. Usually left empty for a new session. + expand : callable or None, optional + Callback used to expand generated configurations. Installed temporarily + by the active file loader and restored when that loader finishes. templates : TemplateManager, optional Template registry shared by files in this session. Defaults to a fresh registry. @@ -302,15 +306,17 @@ def resolve_file(value: str, context: LoadContext | None = None) -> Any: @resolver("template") def resolve_template(value: str, context: LoadContext | None = None) -> Any: """ - Resolve a configuration by template. + Instantiate a template and recursively expand its configuration. Parameters ---------- value : str - Name and arguments of the template. - Must be in the format: NAME,ARG1,ARG2,... or NAME for no arguments. + Name and comma-separated string arguments in the format + ``NAME,ARG1,ARG2,...``, or ``NAME`` for no arguments. A trailing comma + supplies an empty-string argument. Commas in arguments are not escaped. context : LoadContext or None, optional - Active loading context providing the template registry and expansion callback. + Active loading context providing the template registry and expansion + callback. Required for resolution despite the compatibility default. Returns ------- @@ -320,7 +326,10 @@ def resolve_template(value: str, context: LoadContext | None = None) -> Any: Raises ------ PyAMLException - If the template call is invalid or expansion exceeds the recursion limit. + If the active context is missing, the template name is unknown, or + expansion exceeds the recursion limit. Expansion errors also propagate. + PyAMLConfigException + If the number of arguments does not match the template parameters. """ if context is None or context.expand is None: raise PyAMLException("Template resolver requires an active loading context.") @@ -342,13 +351,31 @@ def resolve_template(value: str, context: LoadContext | None = None) -> Any: def load(filename: str, include_locations: bool = False, *, templates: TemplateManager | None = None) -> Union[dict, list]: """ - Load a configuration file. + Load a configuration file, register templates, and expand references. - When include_locations is False, uses the faster C-based YAML loader - and skips including source location metadata. + Parameters + ---------- + filename : str + YAML or JSON filename. Relative paths are resolved against ``ROOT``. + include_locations : bool, optional + Preserve source-location metadata in YAML mappings. Defaults to False, + which uses the faster C-based YAML loader without location metadata. + templates : TemplateManager or None, optional + Registry to share across configuration fragments. If omitted, a fresh + registry is created. Included files share the same registry. - ``templates`` may be supplied to share definitions across configuration - fragments. When omitted, this load gets its own registry. + Returns + ------- + dict or list + Expanded configuration with root-level template definitions removed. + + Raises + ------ + PyAMLException + If the file format is unsupported, parsing fails, or reference + expansion fails. + PyAMLConfigException + If template registration or argument-count validation fails. """ # Create a new context @@ -401,6 +428,8 @@ class ConfigLoader(ABC): ------- expand(obj) Recursively expand configuration values. + register_templates(config) + Register and remove root-level template definitions before expansion. load() Load and parse the current configuration file. """ @@ -574,6 +603,23 @@ def _expand_list(self, items: list) -> list: return expanded def register_templates(self, config): + """ + Register root-level templates in the current loading context. + + Parameters + ---------- + config : object + Parsed configuration. For dictionaries, the ``templates`` section + is removed in place and its definitions are registered. Other + values are left unchanged. + + Raises + ------ + PyAMLConfigException + If a template name is already registered in this context's registry. + KeyError + If a definition lacks ``name``, ``parameters``, or ``config``. + """ if not isinstance(config, dict): return @@ -604,7 +650,7 @@ class YAMLLoader(ConfigLoader): Methods ------- load() - Parse the YAML file and expand nested configuration references. + Parse YAML, register root-level templates, and expand references. """ def __init__(self, path: Path, context: LoadContext): @@ -614,7 +660,7 @@ def __init__(self, path: Path, context: LoadContext): self._loader = SafeLineLoader if context.include_locations else CLoader def load(self) -> Union[dict, list]: - """Parse the YAML file and expand nested configuration references.""" + """Parse YAML, register root-level templates, and expand configuration references.""" logger.log(logging.DEBUG, f"Loading YAML file '{self.path}'") with open(self.path) as file: @@ -641,7 +687,7 @@ class JSONLoader(ConfigLoader): Methods ------- load() - Parse the JSON file and expand nested configuration references. + Parse JSON, register root-level templates, and expand references. """ def __init__(self, path: Path, context: LoadContext): @@ -650,7 +696,7 @@ def __init__(self, path: Path, context: LoadContext): super().__init__(path, context) def load(self) -> Union[dict, list]: - """Parse the JSON file and expand nested configuration references.""" + """Parse JSON, register root-level templates, and expand configuration references.""" logger.log(logging.DEBUG, f"Loading JSON file '{self.path}'") with open(self.path) as file: diff --git a/pyaml/configuration/manager.py b/pyaml/configuration/manager.py index b7626033..94998b94 100644 --- a/pyaml/configuration/manager.py +++ b/pyaml/configuration/manager.py @@ -290,12 +290,14 @@ def replace(self, category: str, element: dict) -> None: def clear(self, category: str | None = None) -> None: r""" - Clear the aggregated state, or a single root field/category. + Clear all configuration state and templates, or one root field/category. Parameters ---------- category : str, optional - If provided, only that category or root field is cleared. + If provided, only that category or root field is cleared and + template definitions are retained. If omitted, all aggregated + state and this manager's template registry are cleared. Examples -------- diff --git a/pyaml/configuration/template.py b/pyaml/configuration/template.py index e5e01813..d1bc6440 100644 --- a/pyaml/configuration/template.py +++ b/pyaml/configuration/template.py @@ -1,3 +1,11 @@ +""" +Store and instantiate parameterized configuration templates. + +Each registry owns its definitions. Generation substitutes positional arguments +into a copy of the template; the configuration loader expands any remaining +file, environment, or template references. +""" + import copy import logging import re @@ -8,6 +16,27 @@ def substitute(obj, arguments): + """ + Recursively substitute named placeholders in configuration values. + + Parameters + ---------- + obj : object + Configuration value to process. Dictionaries and lists are traversed; + dictionary keys and non-string scalar values are left unchanged. + arguments : dict[str, object] + Parameter names mapped to replacement values, converted to strings. + + Returns + ------- + object + Configuration with substitutions applied and new dictionaries and lists. + + Notes + ----- + Replacements use ``{name}`` placeholders and follow argument insertion order. + Text inserted by one replacement can be modified by a later replacement. + """ if isinstance(obj, dict): return {key: substitute(value, arguments) for key, value in obj.items()} if isinstance(obj, list): @@ -20,13 +49,45 @@ def substitute(obj, arguments): class TemplateManager: - """Store template definitions for one configuration manager or loading session.""" + """ + Store template definitions for one configuration manager or loading session. + + Definitions are isolated between instances and copied on registration and + generation so callers can modify their configurations independently. + + Methods + ------- + add(name, parameters, config) + Register a named template with ordered parameters. + generate(name, *args) + Substitute positional arguments into a copy of a template. + clear() + Remove all definitions from this registry. + """ def __init__(self): + """Initialize an empty template registry.""" self.template_parameters: dict[str, list[str]] = {} self.template_codes: dict[str, dict] = {} def add(self, name: str, parameters: list[str], config: dict): + """ + Register a template, copying its parameters and configuration. + + Parameters + ---------- + name : str + Template name, unique within this registry. + parameters : list[str] + Parameter names in the order expected by :meth:`generate`. + config : dict + Configuration body containing ``{parameter}`` placeholders. + + Raises + ------ + PyAMLConfigException + If the name is already registered. + """ if name in self.template_codes: raise PyAMLConfigException(f"Template '{name}' has already been registered.") @@ -34,6 +95,35 @@ def add(self, name: str, parameters: list[str], config: dict): self.template_codes[name] = copy.deepcopy(config) def generate(self, name: str, *args): + """ + Generate a configuration by substituting positional arguments. + + Parameters + ---------- + name : str + Name of a registered template. + *args : object + Values corresponding to the template's ordered parameters. + Values are converted to strings during substitution. + + Returns + ------- + dict + Independent configuration with placeholders replaced. Resolver + expressions and file references are left for the loader to expand. + + Raises + ------ + KeyError + If the template name is not registered. + PyAMLConfigException + If the number of arguments does not match the parameters. + + Notes + ----- + Arguments containing brace-delimited placeholders produce a logging + warning because sequential substitution may modify their contents. + """ number_of_parameters = len(self.template_parameters[name]) number_of_arguments = len(args) @@ -59,5 +149,6 @@ def generate(self, name: str, *args): return config def clear(self): + """Remove all definitions from this registry without affecting other instances.""" self.template_parameters.clear() self.template_codes.clear() From 702bda349634cf8a5bd863f9e57392579261d629 Mon Sep 17 00:00:00 2001 From: kparasch Date: Wed, 30 Sep 2026 16:47:25 +0200 Subject: [PATCH 34/49] fix typehints --- pyaml/configuration/template.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pyaml/configuration/template.py b/pyaml/configuration/template.py index d1bc6440..90ff3850 100644 --- a/pyaml/configuration/template.py +++ b/pyaml/configuration/template.py @@ -9,13 +9,15 @@ import copy import logging import re +from collections.abc import Mapping +from typing import Any from ..common.exception import PyAMLConfigException logger = logging.getLogger(__name__) -def substitute(obj, arguments): +def substitute(obj: Any, arguments: Mapping[str, object]) -> Any: """ Recursively substitute named placeholders in configuration values. @@ -24,7 +26,7 @@ def substitute(obj, arguments): obj : object Configuration value to process. Dictionaries and lists are traversed; dictionary keys and non-string scalar values are left unchanged. - arguments : dict[str, object] + arguments : Mapping[str, object] Parameter names mapped to replacement values, converted to strings. Returns @@ -65,12 +67,12 @@ class TemplateManager: Remove all definitions from this registry. """ - def __init__(self): + def __init__(self) -> None: """Initialize an empty template registry.""" self.template_parameters: dict[str, list[str]] = {} - self.template_codes: dict[str, dict] = {} + self.template_codes: dict[str, dict[str, Any]] = {} - def add(self, name: str, parameters: list[str], config: dict): + def add(self, name: str, parameters: list[str], config: dict[str, Any]) -> None: """ Register a template, copying its parameters and configuration. @@ -80,7 +82,7 @@ def add(self, name: str, parameters: list[str], config: dict): Template name, unique within this registry. parameters : list[str] Parameter names in the order expected by :meth:`generate`. - config : dict + config : dict[str, Any] Configuration body containing ``{parameter}`` placeholders. Raises @@ -94,7 +96,7 @@ def add(self, name: str, parameters: list[str], config: dict): self.template_parameters[name] = list(parameters) self.template_codes[name] = copy.deepcopy(config) - def generate(self, name: str, *args): + def generate(self, name: str, *args: object) -> dict[str, Any]: """ Generate a configuration by substituting positional arguments. @@ -108,7 +110,7 @@ def generate(self, name: str, *args): Returns ------- - dict + dict[str, Any] Independent configuration with placeholders replaced. Resolver expressions and file references are left for the loader to expand. @@ -134,7 +136,7 @@ def generate(self, name: str, *args): ) # name the arguments by position - arguments_dict = {} + arguments_dict: dict[str, object] = {} for arg_name, arg_value in zip(self.template_parameters[name], args, strict=True): # check if {...} is included in any of the arguments, and a raise a warning if so. if re.search(r"\{[^{}]+\}", str(arg_value)): @@ -148,7 +150,7 @@ def generate(self, name: str, *args): return config - def clear(self): + def clear(self) -> None: """Remove all definitions from this registry without affecting other instances.""" self.template_parameters.clear() self.template_codes.clear() From 4ef3b7876dab32129df8f3e66db6c57c8ca5992c Mon Sep 17 00:00:00 2001 From: kparasch Date: Wed, 30 Sep 2026 17:09:31 +0200 Subject: [PATCH 35/49] rename template_codes to template_configs --- pyaml/configuration/template.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyaml/configuration/template.py b/pyaml/configuration/template.py index 90ff3850..7cf5d2a9 100644 --- a/pyaml/configuration/template.py +++ b/pyaml/configuration/template.py @@ -70,7 +70,7 @@ class TemplateManager: def __init__(self) -> None: """Initialize an empty template registry.""" self.template_parameters: dict[str, list[str]] = {} - self.template_codes: dict[str, dict[str, Any]] = {} + self.template_configs: dict[str, dict[str, Any]] = {} def add(self, name: str, parameters: list[str], config: dict[str, Any]) -> None: """ @@ -90,11 +90,11 @@ def add(self, name: str, parameters: list[str], config: dict[str, Any]) -> None: PyAMLConfigException If the name is already registered. """ - if name in self.template_codes: + if name in self.template_configs: raise PyAMLConfigException(f"Template '{name}' has already been registered.") self.template_parameters[name] = list(parameters) - self.template_codes[name] = copy.deepcopy(config) + self.template_configs[name] = copy.deepcopy(config) def generate(self, name: str, *args: object) -> dict[str, Any]: """ @@ -146,11 +146,11 @@ def generate(self, name: str, *args: object) -> dict[str, Any]: ) arguments_dict[arg_name] = arg_value - config = substitute(copy.deepcopy(self.template_codes[name]), arguments_dict) + config = substitute(copy.deepcopy(self.template_configs[name]), arguments_dict) return config def clear(self) -> None: """Remove all definitions from this registry without affecting other instances.""" self.template_parameters.clear() - self.template_codes.clear() + self.template_configs.clear() From 49a49a55fc4e0b30fa1f79b3a3da14b721b78719 Mon Sep 17 00:00:00 2001 From: Alexis Gamelin Date: Thu, 1 Oct 2026 10:46:36 +0200 Subject: [PATCH 36/49] Allow for BPM with a single position Solve #437 Add a new CSSparseScalarAggregator class to allow for aggregator with missing values. --- pyaml/control/abstract_impl.py | 103 +++++++++++++++++++++++++--- pyaml/control/controlsystem.py | 11 +-- tests/bpm/test_bpm_controlsystem.py | 103 ++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 13 deletions(-) diff --git a/pyaml/control/abstract_impl.py b/pyaml/control/abstract_impl.py index 077a819d..96024762 100644 --- a/pyaml/control/abstract_impl.py +++ b/pyaml/control/abstract_impl.py @@ -294,6 +294,89 @@ def nb_device(self) -> int: # ------------------------------------------------------------------------------ +class CSSparseScalarAggregator(CSScalarAggregator): + """ + Scalar aggregator whose slots may have no device. + + Only existing devices are added to the backend aggregator, so they are + still read in one grouped call. Values are scattered back to their slots + and missing slots read NaN. + + Parameters + ---------- + devs : DeviceAccessList + Backend aggregator holding the existing devices. + + Methods + ------- + add_devices(devices) + Add one or more slots, ``None`` for a slot without device. + set(value) + Write the values of the slots that have a device. + set_and_wait(value) + Write the values of the slots that have a device and wait. + get() + Read all slots, NaN for slots without device. + readback() + Read all slot readbacks, NaN for slots without device. + nb_device() + Return the number of slots. + """ + + def __init__(self, devs: DeviceAccessList): + """ + Initialize the CSSparseScalarAggregator. + """ + super().__init__(devs) + self._nb_slots = 0 + self._slots: list[int] = [] + + def add_devices(self, devices: DeviceAccess | None | list[DeviceAccess | None]): + """ + Add one or more slots, ``None`` for a slot without device. + + Parameters + ---------- + devices : DeviceAccess | None | list[DeviceAccess | None] + Control-system device or devices to manage. + """ + for d in devices if isinstance(devices, list) else [devices]: + if d is not None: + self._devs.add_devices(d) + self._slots.append(self._nb_slots) + self._nb_slots += 1 + + def _scatter(self, values: NDArray[np.float64]) -> NDArray[np.float64]: + out = np.full(self._nb_slots, np.nan) + out[self._slots] = values + return out + + def set(self, value: NDArray[np.float64]): + """Write the values of the slots that have a device.""" + if self._slots: + self._devs.set(np.asarray(value)[self._slots]) + + def set_and_wait(self, value: NDArray[np.float64]): + """Write the values of the slots that have a device and wait.""" + if self._slots: + self._devs.set_and_wait(np.asarray(value)[self._slots]) + + def get(self) -> NDArray[np.float64]: + """Read all slots, NaN for slots without device.""" + return self._scatter(self._devs.get() if self._slots else []) + + def readback(self) -> np.array: + """Read all slot readbacks, NaN for slots without device.""" + return self._scatter(self._devs.readback() if self._slots else []) + + def nb_device(self) -> int: + """Return the number of slots.""" + return self._nb_slots + + +# ------------------------------------------------------------------------------ + + class CSStrengthScalarAggregator(CSScalarAggregator): """ Aggregate magnet strengths while avoiding duplicate hardware writes. @@ -801,20 +884,22 @@ class RBpmArray(abstract.ReadFloatArray): Parameters ---------- - hDev : DeviceAccess - Device providing the horizontal BPM position. - vDev : DeviceAccess - Device providing the vertical BPM position. + hDev : DeviceAccess | None + Device providing the horizontal BPM position, ``None`` for a BPM + without horizontal plane. + vDev : DeviceAccess | None + Device providing the vertical BPM position, ``None`` for a BPM + without vertical plane. Methods ------- get() - Return horizontal and vertical BPM positions. + Return horizontal and vertical BPM positions (NaN for a missing plane). unit() Return the unit reported by the BPM device. """ - def __init__(self, hDev: DeviceAccess, vDev: DeviceAccess): + def __init__(self, hDev: DeviceAccess | None, vDev: DeviceAccess | None): """ Initialize the RBpmArray. """ @@ -822,14 +907,14 @@ def __init__(self, hDev: DeviceAccess, vDev: DeviceAccess): self._vDev = vDev def get(self) -> np.array: - """Return horizontal and vertical BPM positions.""" - return np.array([self._hDev.get(), self._vDev.get()]) + """Return horizontal and vertical BPM positions (NaN for a missing plane).""" + return np.array([d.get() if d is not None else np.nan for d in (self._hDev, self._vDev)], dtype=float) # Gets the unit of the value Assume that x and y, offsets and positions # have the same unit def unit(self) -> str: """Return the unit reported by the BPM device.""" - return self._hDev.unit() + return (self._hDev if self._hDev is not None else self._vDev).unit() # ------------------------------------------------------------------------------ diff --git a/pyaml/control/controlsystem.py b/pyaml/control/controlsystem.py index 9199f98d..23876d77 100644 --- a/pyaml/control/controlsystem.py +++ b/pyaml/control/controlsystem.py @@ -18,6 +18,7 @@ from ..configuration.unbound_element import UnboundElement from ..control.abstract_impl import ( CSScalarAggregator, + CSSparseScalarAggregator, CSStrengthScalarAggregator, RBetatronTuneArray, RBpmArray, @@ -197,12 +198,14 @@ def create_bpm_aggregators(self, bpms: list[BPM]) -> list[ScalarAggregator | Non ------- list[ScalarAggregator | None] Aggregators for combined, horizontal, and vertical positions. + A plane without device (e.g. the horizontal plane of a + vertical-only XBPM) reads NaN, the other devices are still read + in one grouped call. """ - agg = self._create_scalar_aggregator() - aggh = self._create_scalar_aggregator() - aggv = self._create_scalar_aggregator() - if agg is None or aggh is None or aggv is None: + aggs = [self.get_aggregator() for _ in range(3)] + if any(a is None for a in aggs): return [None, None, None] + agg, aggh, aggv = (CSSparseScalarAggregator(a) for a in aggs) for b in bpms: devs = self.get_devices_access(b.get_pos_devices()) agg.add_devices(devs) diff --git a/tests/bpm/test_bpm_controlsystem.py b/tests/bpm/test_bpm_controlsystem.py index 13321ca1..4dfea2a0 100644 --- a/tests/bpm/test_bpm_controlsystem.py +++ b/tests/bpm/test_bpm_controlsystem.py @@ -94,3 +94,106 @@ def test_controlsystem_bpm_position_indexed(install_test_package): bpm = sr.live.bpm.get("BPM_C01-04") assert np.allclose(bpm.positions.get(), np.array([0.0, 1.0])) + + +def _single_plane_bpm_config() -> dict: + """Two full BPMs and one vertical-only XBPM, grouped in one BPM array.""" + entries = [] + for key in ["c01-01/H", "c01-01/V", "c01-02/H", "c01-02/V", "xbpm/V"]: + entries.append( + { + "type": "tango.pyaml.static_catalog_entry", + "key": key, + "device": {"type": "tango.pyaml.attribute_read_only", "attribute": f"sr/bpm/{key}", "unit": "mm"}, + } + ) + return { + "type": "pyaml.accelerator", + "facility": "ESRF", + "machine": "sr", + "energy": 6e9, + "data_folder": "/data/store", + "controls": [ + { + "type": "tango.pyaml.controlsystem", + "tango_host": "ebs-simu-3:10000", + "name": "live", + "catalog": {"type": "tango.pyaml.static_catalog", "entries": entries}, + } + ], + "arrays": [{"type": "pyaml.arrays.bpm", "name": "BPM", "elements": ["BPM_1", "BPM_2", "XBPM"]}], + "devices": [ + {"type": "pyaml.bpm.bpm", "name": "BPM_1", "x_pos": "c01-01/H", "y_pos": "c01-01/V"}, + {"type": "pyaml.bpm.bpm", "name": "BPM_2", "x_pos": "c01-02/H", "y_pos": "c01-02/V"}, + {"type": "pyaml.bpm.bpm", "name": "XBPM", "y_pos": "xbpm/V"}, + ], + } + + +@pytest.mark.parametrize( + "install_test_package", + [{"name": "tango-pyaml", "path": "tests/dummy_cs/tango-pyaml"}], + indirect=True, +) +def test_controlsystem_bpm_single_plane(install_test_package): + from tango.pyaml.attribute_store import set_attribute + + set_attribute("sr/bpm/xbpm/V", 0.3, unit="mm") + sr = Accelerator.from_dict(_single_plane_bpm_config()) + xbpm = sr.live.bpm.get("XBPM") + + pos = xbpm.positions.get() + assert np.isnan(pos[0]) + assert pos[1] == pytest.approx(0.3) + assert xbpm.positions.unit() == "mm" + + +@pytest.mark.parametrize( + "install_test_package", + [{"name": "tango-pyaml", "path": "tests/dummy_cs/tango-pyaml"}], + indirect=True, +) +def test_controlsystem_bpm_array_with_single_plane_bpm(install_test_package): + from tango.pyaml.attribute_store import set_attribute + + for name, value in [("c01-01/H", 0.1), ("c01-01/V", -0.1), ("c01-02/H", 0.2), ("c01-02/V", -0.2), ("xbpm/V", 0.3)]: + set_attribute(f"sr/bpm/{name}", value, unit="mm") + sr = Accelerator.from_dict(_single_plane_bpm_config()) + bpms = sr.live.bpms.get("BPM") + + pos = bpms.positions.get() + assert pos.shape == (3, 2) + assert np.isnan(pos[2, 0]) + assert np.allclose(pos[:2], [[0.1, -0.1], [0.2, -0.2]]) + assert pos[2, 1] == pytest.approx(0.3) + + h = bpms.h.get() + assert np.allclose(h[:2], [0.1, 0.2]) + assert np.isnan(h[2]) + assert np.allclose(bpms.v.get(), [-0.1, -0.2, 0.3]) + + # The missing plane must not disable the grouped reads + agg, aggh, aggv = sr.live.create_bpm_aggregators(list(bpms)) + assert [agg.nb_device(), aggh.nb_device(), aggv.nb_device()] == [6, 3, 3] + assert np.allclose(agg.get(), pos.flatten(), equal_nan=True) + assert np.allclose(aggh.get(), h, equal_nan=True) + assert np.allclose(aggv.get(), [-0.1, -0.2, 0.3]) + + +@pytest.mark.parametrize( + "install_test_package", + [{"name": "tango-pyaml", "path": "tests/dummy_cs/tango-pyaml"}], + indirect=True, +) +def test_controlsystem_bpm_array_without_horizontal_plane(install_test_package): + from tango.pyaml.attribute_store import set_attribute + + set_attribute("sr/bpm/xbpm/V", 0.3, unit="mm") + config = _single_plane_bpm_config() + config["arrays"][0]["elements"] = ["XBPM"] + sr = Accelerator.from_dict(config) + bpms = sr.live.bpms.get("BPM") + + assert np.isnan(bpms.h.get()).all() + assert np.allclose(bpms.v.get(), [0.3]) + assert bpms.positions.get().shape == (1, 2) From 6f3d0f5838ebd31922a424cf7f26342a0b7fb8b5 Mon Sep 17 00:00:00 2001 From: Alexis Gamelin Date: Thu, 1 Oct 2026 11:17:47 +0200 Subject: [PATCH 37/49] Fix SerializedMagnets attachment to a control system (#458) All magnets of a serialized group share one power supply, so resolve it once instead of indexing one device per magnet (IndexError). Virtual magnet strengths now carry their position in the group so that CSStrengthScalarAggregator maps each one to the right strength of the serialized model; previously every magnet hit slot 0 and only 1/N of a change reached the power supply. Co-Authored-By: Claude Opus 5.5 --- pyaml/control/abstract_impl.py | 41 ++++++++++++- pyaml/control/controlsystem.py | 13 ++--- tests/config/sr_serialized_magnets_cs.yaml | 57 ++++++++++++++++++ tests/magnet/test_serialized_magnets_cs.py | 67 ++++++++++++++++++++++ 4 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 tests/config/sr_serialized_magnets_cs.yaml create mode 100644 tests/magnet/test_serialized_magnets_cs.py diff --git a/pyaml/control/abstract_impl.py b/pyaml/control/abstract_impl.py index 077a819d..58169413 100644 --- a/pyaml/control/abstract_impl.py +++ b/pyaml/control/abstract_impl.py @@ -347,7 +347,10 @@ def add_magnet(self, magnet: Magnet, devs: list[DeviceAccess]): devs : list[DeviceAccess] Hardware devices associated with the magnet model. """ - strengthIndex = magnet.strength.index() if isinstance(magnet.strength, abstract.RWMapper) else 0 + # Magnets of a serialized group share the serialized model (one power supply for N strengths): + # the power-supply current is computed by the model from the N strengths (mean of the implied currents). + is_indexed = isinstance(magnet.strength, (abstract.RWMapper, RWSerializedStrengthScalar)) + strengthIndex = magnet.strength.index() if is_indexed else 0 if magnet.model not in self.__models: index = len(self.__models) self.__models.append(magnet.model) @@ -628,6 +631,42 @@ def set_magnet_rigidity(self, brho: np.double): self.__model.set_magnet_rigidity(brho) +class RWSerializedStrengthScalar(RWStrengthScalar): + """ + Expose the strength of one magnet of a serialized group. + + All magnets of a serialized group share one power supply. This accessor + converts with the magnet's own sub-model and remembers the magnet position + in the group so that aggregators can map it to the right strength of the + serialized model. + + Parameters + ---------- + model : MagnetModel + Sub-model of the magnet, used for strength conversion and units. + dev : DeviceAccess + Power-supply device shared by the serialized group. + idx : int + Zero-based position of the magnet in the serialized group. + + Methods + ------- + index() + Return the position of the magnet in the serialized group. + """ + + def __init__(self, model: MagnetModel, dev: DeviceAccess, idx: int): + """ + Initialize the RWSerializedStrengthScalar. + """ + super().__init__(model, dev) + self.__idx = idx + + def index(self) -> int: + """Return the position of the magnet in the serialized group.""" + return self.__idx + + # ------------------------------------------------------------------------------ diff --git a/pyaml/control/controlsystem.py b/pyaml/control/controlsystem.py index 9199f98d..e7145404 100644 --- a/pyaml/control/controlsystem.py +++ b/pyaml/control/controlsystem.py @@ -28,6 +28,7 @@ RWRFFrequencyScalar, RWRFPhaseScalar, RWRFVoltageScalar, + RWSerializedStrengthScalar, RWStrengthArray, RWStrengthScalar, ) @@ -226,16 +227,14 @@ def _fill_combined_function_magnet(self, magnet: CombinedFunctionMagnet) -> None self.magnet.add(virtual_magnet) def _fill_serialized_magnets(self, magnets: SerializedMagnets) -> None: - devices = self.get_devices_access(magnets.model.get_device_names()) + # All magnets of the group share the same power supply + device = self.get_device_access(magnets.model.get_device_names()[0]) currents = [] strengths = [] for index in range(magnets.get_nb_magnets()): - currents.append( - RWHardwareScalar(magnets.model.get_sub_model(index), devices[index]) if magnets.model.has_hardware() else None - ) - strengths.append( - RWStrengthScalar(magnets.model.get_sub_model(index), devices[index]) if magnets.model.has_physics() else None - ) + sub_model = magnets.model.get_sub_model(index) + currents.append(RWHardwareScalar(sub_model, device) if magnets.model.has_hardware() else None) + strengths.append(RWSerializedStrengthScalar(sub_model, device, index) if magnets.model.has_physics() else None) attached_magnets = magnets.attach(self, strengths, currents) self.serialized_magnet.add(attached_magnets[0]) for magnet in attached_magnets[1:]: diff --git a/tests/config/sr_serialized_magnets_cs.yaml b/tests/config/sr_serialized_magnets_cs.yaml new file mode 100644 index 00000000..2db7546f --- /dev/null +++ b/tests/config/sr_serialized_magnets_cs.yaml @@ -0,0 +1,57 @@ +type: pyaml.accelerator +facility: ESRF +machine: sr +energy: 6e9 +simulators: + - type: pyaml.lattice.simulator + lattice: sr/lattices/ebs.mat + name: design +controls: + - type: tango.pyaml.controlsystem + tango_host: ebs-simu-3:10000 + name: live + catalog: + type: tango.pyaml.tango_catalog +data_folder: /data/store +arrays: + - type: pyaml.arrays.magnet + name: ALL_SERIALIZED + elements: + - QF1A-C* + - QD2A-C* +devices: + - type: pyaml.magnet.serialized_magnet + name: QF1A + function: B1 + elements: + - QF1A-C05 + - QF1A-C06 + - QF1A-C07 + - QF1A-C08 + model: + type: pyaml.magnet.linear_serialized_model + calibration_factors: [1.0, 1.01, 0.99, 1.02] + calibration_offsets: 0.0 + unit: m-1 + hardware_unit: A + curves: + type: pyaml.magnet.csvcurve + file: sr/magnet_models/QF1_strength.csv + powerconverter: srmag/ps-qf1a/series/current + - type: pyaml.magnet.serialized_magnet + name: QD2A + function: B1 + elements: + - QD2A-C05 + - QD2A-C06 + - QD2A-C07 + model: + type: pyaml.magnet.linear_serialized_model + calibration_factors: 1.0 + calibration_offsets: 0.0 + unit: m-1 + hardware_unit: A + curves: + type: pyaml.magnet.csvcurve + file: sr/magnet_models/QD2_strength.csv + powerconverter: srmag/ps-qd2a/series/current diff --git a/tests/magnet/test_serialized_magnets_cs.py b/tests/magnet/test_serialized_magnets_cs.py new file mode 100644 index 00000000..cd4f2998 --- /dev/null +++ b/tests/magnet/test_serialized_magnets_cs.py @@ -0,0 +1,67 @@ +import numpy as np +import pytest + +from pyaml.accelerator import Accelerator +from pyaml.magnet.serialized_magnet import SerializedMagnets + +CONFIG = "tests/config/sr_serialized_magnets_cs.yaml" +TANGO_DUMMY = [{"name": "tango-pyaml", "path": "tests/dummy_cs/tango-pyaml"}] + + +def _sub_strengths(family: SerializedMagnets, current: float) -> np.ndarray: + return np.array([family.model.get_sub_model(i).compute_strengths([current])[0] for i in range(family.get_nb_magnets())]) + + +@pytest.mark.parametrize("install_test_package", TANGO_DUMMY, indirect=True) +def test_serialized_magnets_attach_to_control_system(install_test_package): + sr: Accelerator = Accelerator.load(CONFIG) + family: SerializedMagnets = sr.live.serialized_magnet.get("QF1A") + magnets = family.get_magnets() + + assert family.get_nb_magnets() == 4 + assert [m.get_name() for m in magnets] == ["QF1A-C05", "QF1A-C06", "QF1A-C07", "QF1A-C08"] + for m in magnets: + assert sr.live.magnet.get(m.get_name()) is m + + +@pytest.mark.parametrize("install_test_package", TANGO_DUMMY, indirect=True) +def test_serialized_magnets_share_power_supply(install_test_package): + sr: Accelerator = Accelerator.load(CONFIG) + family: SerializedMagnets = sr.live.serialized_magnet.get("QF1A") + magnets = family.get_magnets() + + magnets[2].hardware.set(80.0) + assert np.allclose([m.hardware.get() for m in magnets], 80.0) + assert np.allclose([m.strength.get() for m in magnets], _sub_strengths(family, 80.0)) + + # Setting the strength of one magnet drives the shared power supply with that magnet's own sub-model + magnets[1].strength.set(0.5) + current = family.model.get_sub_model(1).compute_hardware_values([0.5])[0] + assert np.allclose([m.hardware.get() for m in magnets], current) + assert magnets[1].strength.get() == pytest.approx(0.5) + + # The other family is on its own power supply + assert np.allclose([m.hardware.get() for m in sr.live.serialized_magnet.get("QD2A").get_magnets()], 0.0) + + +@pytest.mark.parametrize("install_test_package", TANGO_DUMMY, indirect=True) +def test_serialized_magnets_aggregator(install_test_package): + sr: Accelerator = Accelerator.load(CONFIG) + qf1a: SerializedMagnets = sr.live.serialized_magnet.get("QF1A") + qd2a: SerializedMagnets = sr.live.serialized_magnet.get("QD2A") + qf1a.get_magnets()[0].hardware.set(30.0) + qd2a.get_magnets()[0].hardware.set(20.0) + + array = sr.live.magnets.get("ALL_SERIALIZED") + expected = np.concatenate([_sub_strengths(qf1a, 30.0), _sub_strengths(qd2a, 20.0)]) + assert np.allclose(array.strengths.get(), expected) + assert np.allclose(array.hardwares.get(), [30.0] * 4 + [20.0] * 3) + + # Doubling every magnet of a series moves its power supply to the current of the doubled strength (no 1/N dilution) + array.strengths.set(2 * array.strengths.get()) + assert np.allclose(array.strengths.get(), 2 * expected) + qf1a_current = qf1a.model.get_sub_model(0).compute_hardware_values([2 * expected[0]])[0] + assert np.allclose(array.hardwares.get()[:4], qf1a_current) + + array.hardwares.set(array.hardwares.get()) + assert np.allclose(array.strengths.get(), 2 * expected) From 18f8e62894df0aa1adf14f2f048f21ab0e7368ea Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Thu, 1 Oct 2026 14:12:26 +0200 Subject: [PATCH 38/49] ElementHolder.get() resolves named arrays of any type; drop superseded get_element/get_elements/get_all_elements/get_betatron_tune_monitor/get_*_tuning --- .../OphydDevice_examples/tune_ophyd_device.py | 2 +- examples/use_cases/01-tune_correction.ipynb | 2 +- examples/use_cases/01-tune_correction.py | 2 +- pyaml/arrays/element.py | 2 +- pyaml/arrays/element_array.py | 4 +- pyaml/common/holders/diagnostic_holder.py | 9 +- pyaml/common/holders/element_holder.py | 234 +++--------------- pyaml/tuning_tools/bba2.py | 6 +- pyaml/tuning_tools/chromaticity_monitor.py | 2 +- pyaml/tuning_tools/tune.py | 2 +- pyaml/tuning_tools/tune_response_matrix.py | 2 +- tests/arrays/test_array_selection_types.py | 2 +- tests/arrays/test_arrays.py | 6 +- tests/arrays/test_arrays_ops.py | 12 +- .../common/test_element_holder_collection.py | 27 +- .../diagnostics/test_diagnostic_accessors.py | 8 +- tests/integration/test_dt4acc_twin_smoke.py | 4 +- tests/magnet/test_serialized_magnets.py | 12 +- tests/test_accelerator_load.py | 8 +- tests/test_load_conf_with_code.py | 2 +- tests/tuning_tools/test_tool_accessors.py | 2 +- tests/tuning_tools/test_tune.py | 2 +- tests/tuning_tools/test_tune_hardware.py | 2 +- tests/tuning_tools/test_tune_monitor.py | 4 +- 24 files changed, 92 insertions(+), 266 deletions(-) diff --git a/examples/other_examples/OphydDevice_examples/tune_ophyd_device.py b/examples/other_examples/OphydDevice_examples/tune_ophyd_device.py index f98c1922..bb22a588 100644 --- a/examples/other_examples/OphydDevice_examples/tune_ophyd_device.py +++ b/examples/other_examples/OphydDevice_examples/tune_ophyd_device.py @@ -179,6 +179,6 @@ def unit(self) -> str: # Main code sr = Accelerator.from_dict(acc_config) -tm1 = sr.live.get_betatron_tune_monitor("MY_TUNE_MONITOR") +tm1 = sr.live.diagnostic.get("MY_TUNE_MONITOR") print(tm1.tune.get()) print(tm1.frequency.get()) diff --git a/examples/use_cases/01-tune_correction.ipynb b/examples/use_cases/01-tune_correction.ipynb index cf6e3471..4e20ff1f 100644 --- a/examples/use_cases/01-tune_correction.ipynb +++ b/examples/use_cases/01-tune_correction.ipynb @@ -207,7 +207,7 @@ } ], "source": [ - "tune_monitor = SR.get_betatron_tune_monitor(\"BETATRON_TUNE\")\n", + "tune_monitor = SR.diagnostic.get(\"BETATRON_TUNE\")\n", "print(f\"Current tune: {tune_monitor.tune.get()}\")\n", "tune_monitor # string representation" ] diff --git a/examples/use_cases/01-tune_correction.py b/examples/use_cases/01-tune_correction.py index fc84e929..1525b993 100644 --- a/examples/use_cases/01-tune_correction.py +++ b/examples/use_cases/01-tune_correction.py @@ -80,7 +80,7 @@ # In[4]: -tune_monitor = SR.get_betatron_tune_monitor("BETATRON_TUNE") +tune_monitor = SR.diagnostic.get("BETATRON_TUNE") print(f"Current tune: {tune_monitor.tune.get()}") print(tune_monitor) # string representation diff --git a/pyaml/arrays/element.py b/pyaml/arrays/element.py index 6483f03d..4b93276a 100644 --- a/pyaml/arrays/element.py +++ b/pyaml/arrays/element.py @@ -65,7 +65,7 @@ def fill_array(self, holder: ElementHolder): .. code-block:: python >>> elt_cfg.fill_array(sr.design) - >>> names = sr.design.get_elements("MyArray").names() + >>> names = sr.design.get("MyArray").names() >>> print(names) ['BPM_C04-01', 'SH1A-C04-H'] diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index 26c7b65b..797fc919 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -61,7 +61,7 @@ class ElementArray(list[Element]): .. code-block:: python >>> sr = Accelerator.load("acc.yaml") - >>> elements = sr.design.get_elements("QuadForTune") + >>> elements = sr.design.get("QuadForTune") """ def __init__(self, array_name: str, elements: list[Element], use_aggregator=True): @@ -282,7 +282,7 @@ def __and__(self, other: object): .. code-block:: python - >>> cell1 = sr.live.get_elements("C01") + >>> cell1 = sr.live.get("C01") >>> sexts = sr.live.get_magnets("SEXT") >>> cell1_sext = cell1 & sexts diff --git a/pyaml/common/holders/diagnostic_holder.py b/pyaml/common/holders/diagnostic_holder.py index b78eefbf..fa156c68 100644 --- a/pyaml/common/holders/diagnostic_holder.py +++ b/pyaml/common/holders/diagnostic_holder.py @@ -35,13 +35,6 @@ class DiagnosticHolder: get(name=None) Return a named diagnostic, or all configured diagnostics when no name is given. - Notes - ----- - :meth:`ElementHolder.get_betatron_tune_monitor - ` stays - available as the named, untyped lookup. This holder adds the default-name, - type-validated convenience property. - Examples -------- >>> default_tune_monitor = sr.live.diagnostic.betatron_tune @@ -144,7 +137,7 @@ def betatron_tune(self) -> BetatronTuneMonitor: :class:`~pyaml.diagnostics.tune_monitor.BetatronTuneMonitor`. """ name = "BETATRON_TUNE" - obj = self._peer.get_betatron_tune_monitor(name) + obj = self.get(name) if not isinstance(obj, BetatronTuneMonitor): raise PyAMLException(f"{name}: BetatronTuneMonitor expected but got {type(obj).__name__}") return obj diff --git a/pyaml/common/holders/element_holder.py b/pyaml/common/holders/element_holder.py index 21d303ac..65291b2a 100644 --- a/pyaml/common/holders/element_holder.py +++ b/pyaml/common/holders/element_holder.py @@ -32,14 +32,7 @@ from ...accelerator import Accelerator from ...configuration.unbound_element import UnboundElement from ...tuning_tools.bba import BBA - from ...tuning_tools.chromaticity import Chromaticity - from ...tuning_tools.chromaticity_response_matrix import ChromaticityResponseMatrix - from ...tuning_tools.dispersion import Dispersion from ...tuning_tools.measurement_tool import MeasurementTool - from ...tuning_tools.orbit import Orbit - from ...tuning_tools.orbit_response_matrix import OrbitResponseMatrix - from ...tuning_tools.tune import Tune - from ...tuning_tools.tune_response_matrix import TuneResponseMatrix from ...tuning_tools.tuning_tool import TuningTool @@ -85,36 +78,16 @@ class ElementHolder(metaclass=ABCMeta): Create and register a generic element array. add_element(element) Add an element to the global element store. - get_element(name) - Return a named element from the global store. - get_elements(name) - Return a named generic element array. - get_all_elements() - Return all registered elements in insertion order. - get_betatron_tune_monitor(name) - Return a named betatron tune monitor. + get(name=None) + Return a named element array, or every registered element when no name is given. add_betatron_tune_monitor(tune_monitor) Add a betatron tune monitor to the diagnostics store. add_tool(tool) Add a tuning or measurement tool to the tool store. get_chromaticity_monitor(name) Return a named chromaticity monitor. - get_chromaticity_tuning(name) - Return a named chromaticity tuning tool. - get_crm_tuning(name) - Return a named chromaticity response-matrix tool. - get_tune_tuning(name) - Return a named tune correction tool. - get_trm_tuning(name) - Return a named tune response-matrix tool. - get_orbit_tuning(name) - Return a named orbit correction tool. - get_orm_tuning(name) - Return a named orbit response-matrix tool. get_bba(name) Return a named beam-based alignment tool. - get_dispersion_tuning(name) - Return a named dispersion tuning tool. """ def __init__(self): @@ -211,7 +184,7 @@ def tool(self) -> ToolHolder: def post_init(self): """Run post-initialization hooks for every stored element.""" - for e in self.get_all_elements(): + for e in self._ALL.values(): e.post_init() def fill_device(self, elements: list[Element]): @@ -418,13 +391,27 @@ def _get(self, what, name, array) -> Element: return array[name] # Generic elements - def get(self) -> ElementArray: - """Return all registered elements in insertion order. + def get(self, name: str | None = None) -> ElementArray: + """Return a named element array, or every registered element when no name is given. + + Parameters + ---------- + name : str, optional + Name of the element array to look up, as declared in the configuration. + When omitted, every registered element is returned instead. Returns ------- ElementArray - New unnamed container sharing the registered element references. + The element array registered under ``name``, regardless of its concrete + family (magnet, BPM, combined-function magnet, serialized-magnet, or + generic element array), or a new unnamed container of every registered + element, in insertion order, when ``name`` is omitted. + + Raises + ------ + PyAMLException + If ``name`` is given and no array is registered under it. Notes ----- @@ -436,8 +423,11 @@ def get(self) -> ElementArray: -------- >>> elements = sr.live.get() >>> names = elements.names() + >>> cell08 = sr.live.get("CELL08") """ - return ElementArray("", list(self._ALL.values())) + if name is None: + return ElementArray("", list(self._ALL.values())) + return self._get_array(name) @overload def __getitem__(self, key: int) -> Element: ... @@ -545,7 +535,7 @@ def fill_element_array(self, arrayName: str, elementNames: list[str]): self._fill_array( arrayName, elementNames, - self.get_element, + self._get_element, ElementArray, self._ELEMENT_ARRAYS, ) @@ -561,60 +551,14 @@ def add_element(self, element: Element): """ self._ALL[element.get_name()] = element - def get_element(self, name: str) -> Element: + def _get_element(self, name: str) -> Element: """ - Return a named element from the global store. - - Parameters - ---------- - name : str - Name of the element to look up, as declared in the configuration. - - Returns - ------- - Element - The element registered under ``name``. + Generic single-element resolver used internally to build element arrays. """ return self._get("Element", name, self._ALL) - def get_elements(self, name: str) -> ElementArray: - """ - Return a named generic element array. - - Parameters - ---------- - name : str - Name of the element array to look up, as declared in the configuration. - - Returns - ------- - ElementArray - The element array registered under ``name``. - """ - return self._get("Element array", name, self._ELEMENT_ARRAYS) - - def get_all_elements(self) -> list[Element]: - """Return all registered elements in insertion order.""" - return [value for key, value in self._ALL.items()] - # Tune monitor - def get_betatron_tune_monitor(self, name: str) -> BetatronTuneMonitor: - """ - Return a named betatron tune monitor. - - Parameters - ---------- - name : str - Name of the betatron tune monitor to look up, as declared in the configuration. - - Returns - ------- - BetatronTuneMonitor - The betatron tune monitor registered under ``name``. - """ - return self._get("Diagnostic", name, self._DIAG) - def add_betatron_tune_monitor(self, tune_monitor: Element): """ Add a betatron tune monitor to the diagnostics store. @@ -658,106 +602,6 @@ def get_chromaticity_monitor(self, name: str) -> ChromaticityMonitor: obj = self._get("Chromaticity monitor", name, self._TOOLS) return obj - def get_chromaticity_tuning(self, name: str) -> "Chromaticity": - """ - Return a named chromaticity tuning tool. - - Parameters - ---------- - name : str - Name of the chromaticity tuning tool to look up, as declared in the configuration. - - Returns - ------- - 'Chromaticity' - The chromaticity tuning tool registered under ``name``. - """ - return self._get("Chromaticity tool", name, self._TOOLS) - - def get_crm_tuning(self, name: str) -> "ChromaticityResponseMatrix": - """ - Return a named chromaticity response-matrix tool. - - Parameters - ---------- - name : str - Name of the chromaticity response-matrix tool to look up, as declared in the configuration. - - Returns - ------- - 'ChromaticityResponseMatrix' - The chromaticity response-matrix tool registered under ``name``. - """ - return self._get("ChromaticityResponseMatrix tool", name, self._TOOLS) - - # ---- Tune --------------------------------------------------------- - - def get_tune_tuning(self, name: str) -> "Tune": - """ - Return a named tune correction tool. - - Parameters - ---------- - name : str - Name of the tune correction tool to look up, as declared in the configuration. - - Returns - ------- - 'Tune' - The tune correction tool registered under ``name``. - """ - return self._get("Tune tuning tool", name, self._TOOLS) - - def get_trm_tuning(self, name: str) -> "TuneResponseMatrix": - """ - Return a named tune response-matrix tool. - - Parameters - ---------- - name : str - Name of the tune response-matrix tool to look up, as declared in the configuration. - - Returns - ------- - 'TuneResponseMatrix' - The tune response-matrix tool registered under ``name``. - """ - return self._get("TuneResponseMatrix tool", name, self._TOOLS) - - # ---- Orbit -------------------------------------------------------- - - def get_orbit_tuning(self, name: str) -> "Orbit": - """ - Return a named orbit correction tool. - - Parameters - ---------- - name : str - Name of the orbit correction tool to look up, as declared in the configuration. - - Returns - ------- - 'Orbit' - The orbit correction tool registered under ``name``. - """ - return self._get("Orbit tuning tool", name, self._TOOLS) - - def get_orm_tuning(self, name: str) -> "OrbitResponseMatrix": - """ - Return a named orbit response-matrix tool. - - Parameters - ---------- - name : str - Name of the orbit response-matrix tool to look up, as declared in the configuration. - - Returns - ------- - 'OrbitResponseMatrix' - The orbit response-matrix tool registered under ``name``. - """ - return self._get("OrbitResponseMatrix tool", name, self._TOOLS) - # ---- BBA -------------------------------------------------------- def get_bba(self, name: str) -> "BBA": @@ -776,24 +620,6 @@ def get_bba(self, name: str) -> "BBA": """ return self._get("BBA tool", name, self._TOOLS) - # ---- Dispersive orbit -------------------------------------------- - - def get_dispersion_tuning(self, name: str) -> "Dispersion": - """ - Return a named dispersion tuning tool. - - Parameters - ---------- - name : str - Name of the dispersion tuning tool to look up, as declared in the configuration. - - Returns - ------- - 'Dispersion' - The dispersion tuning tool registered under ``name``. - """ - return self._get("Dispersion tool", name, self._TOOLS) - def _get_array(self, name: str): """ Generic array resolver used by YellowPages. @@ -864,7 +690,7 @@ def _set_energy(self, E: float): Energy in eV """ # Needed by energy dependant element (i.e. magnet coil current calculation) - for m in self.get_all_elements(): + for m in self._ALL.values(): m.set_energy(E) def _set_mcf(self, alphac: float): @@ -877,7 +703,7 @@ def _set_mcf(self, alphac: float): Moment compaction factor """ # Needed by some off energy dependant element (i.e. chromaticty tools) - for m in self.get_all_elements(): + for m in self._ALL.values(): m.set_mcf(alphac) def _set_harmonic(self, h: int): @@ -889,5 +715,5 @@ def _set_harmonic(self, h: int): h : int Harmonic number """ - for m in self.get_all_elements(): + for m in self._ALL.values(): m.set_harmonic(h) diff --git a/pyaml/tuning_tools/bba2.py b/pyaml/tuning_tools/bba2.py index 05544687..5d6e5f2b 100644 --- a/pyaml/tuning_tools/bba2.py +++ b/pyaml/tuning_tools/bba2.py @@ -274,7 +274,7 @@ def quadrupole(self) -> "Magnet": def tune_correction(self) -> "Tune": """Return the tune-correction tool used during alignment.""" self.check_peer() - return self.peer.get_tune_tuning(self.tune_correction_name) + return self.peer.tool.get(self.tune_correction_name) @staticmethod def _x_intercept(x, k, n): @@ -360,8 +360,8 @@ def _init_responses( sth = design.magnet.get(steererhname) stv = design.magnet.get(steerervname) orbit = design.diagnostic.bpms.get(bpmname).positions - tune_design = design.get_tune_tuning(tunename) - tune_live = self._peer.get_tune_tuning(tunename) + tune_design = design.tool.get(tunename) + tune_live = self._peer.tool.get(tunename) # Get tune from live and adjust the model to improve quad response phase tune0 = tune_design.readback() diff --git a/pyaml/tuning_tools/chromaticity_monitor.py b/pyaml/tuning_tools/chromaticity_monitor.py index f9bfa525..c03e284f 100644 --- a/pyaml/tuning_tools/chromaticity_monitor.py +++ b/pyaml/tuning_tools/chromaticity_monitor.py @@ -201,7 +201,7 @@ def __init__( def tune_monitor(self) -> "BetatronTuneMonitor": """Return the betatron tune monitor used for measurements.""" self.check_peer() - return self.peer.get_betatron_tune_monitor(self.betatron_tune_name) + return self.peer.diagnostic.get(self.betatron_tune_name) @property def rf_plant(self) -> "RFPlant": diff --git a/pyaml/tuning_tools/tune.py b/pyaml/tuning_tools/tune.py index 3c336d31..18811bba 100644 --- a/pyaml/tuning_tools/tune.py +++ b/pyaml/tuning_tools/tune.py @@ -153,7 +153,7 @@ def response_matrix(self) -> ResponseMatrixData | None: def tune_monitor(self) -> "BetatronTuneMonitor": """Return the betatron tune monitor used by the correction.""" self.check_peer() - return self.peer.get_betatron_tune_monitor(self.betatron_tune_name) + return self.peer.diagnostic.get(self.betatron_tune_name) @property def quadrupoles(self) -> "MagnetArray": diff --git a/pyaml/tuning_tools/tune_response_matrix.py b/pyaml/tuning_tools/tune_response_matrix.py index 380a4105..fe65e0d1 100644 --- a/pyaml/tuning_tools/tune_response_matrix.py +++ b/pyaml/tuning_tools/tune_response_matrix.py @@ -154,7 +154,7 @@ def quadrupoles(self) -> "MagnetArray": def tune_monitor(self) -> "BetatronTuneMonitor": """Return the betatron tune monitor used for the measurement.""" self.check_peer() - return self.peer.get_betatron_tune_monitor(self.betatron_tune_name) + return self.peer.diagnostic.get(self.betatron_tune_name) def measure( self, diff --git a/tests/arrays/test_array_selection_types.py b/tests/arrays/test_array_selection_types.py index 11e65181..8dce804e 100644 --- a/tests/arrays/test_array_selection_types.py +++ b/tests/arrays/test_array_selection_types.py @@ -44,7 +44,7 @@ def test_magnet_holder_selection_uses_the_same_typing(design): def test_mixed_array_stays_generic_until_only_magnets_are_selected(design): - mixed = design.get_elements("ElArray") + mixed = design.get("ElArray") assert type(mixed[:]) is ElementArray assert mixed[:].names() == mixed.names() diff --git a/tests/arrays/test_arrays.py b/tests/arrays/test_arrays.py index 62f9c202..5bd02a3d 100644 --- a/tests/arrays/test_arrays.py +++ b/tests/arrays/test_arrays.py @@ -164,10 +164,10 @@ def test_arrays(install_test_package): assert np.abs(pos[1][1] - 7.4265634524358045e-06) < 1e-10 # Radom array - elts = sr.design.get_elements("ElArray") + elts = sr.design.get("ElArray") # Create an array that contains all elements - allElts = ElementArray("AllElements", sr.design.get_all_elements()) + allElts = ElementArray("AllElements", sr.design.get()) assert len(allElts) == 11 # Create an array that contains all elements @@ -210,7 +210,7 @@ def test_arrays(install_test_package): # Test dynamic arrays sr: Accelerator = Accelerator.load("tests/config/EBSOrbit.yaml", include_locations=False) - ae = ElementArray("All", sr.design.get_all_elements()) + ae = ElementArray("All", sr.design.get()) acfm = ElementArray("AllCFM", sr.design.combined_function_magnet.all(), use_aggregator=False) bpmC5 = ae["BPM*"][10:20] # All BPM C5 diff --git a/tests/arrays/test_arrays_ops.py b/tests/arrays/test_arrays_ops.py index 5fd706e2..fd8c961f 100644 --- a/tests/arrays/test_arrays_ops.py +++ b/tests/arrays/test_arrays_ops.py @@ -43,7 +43,7 @@ def test_element_array_and_mask_filters_and_is_autotyped_list_mask( sr.design.get_lattice().disable_6d() # "ElArray" is a mixed ElementArray in the dummy config (see existing tests) - elts = sr.design.get_elements("ElArray") + elts = sr.design.get("ElArray") assert isinstance(elts, ElementArray) assert len(elts) > 0 @@ -71,7 +71,7 @@ def test_element_array_and_mask_filters_and_is_autotyped_numpy_mask( sr = accelerator_from_fragments(*sr_configuration_fragments) sr.design.get_lattice().disable_6d() - elts = sr.design.get_elements("ElArray") + elts = sr.design.get("ElArray") assert len(elts) > 0 mask_list = [isinstance(e, Magnet) for e in elts] @@ -97,7 +97,7 @@ def test_element_array_sub_mask_removes_true_inverse_of_and( sr = accelerator_from_fragments(*sr_configuration_fragments) sr.design.get_lattice().disable_6d() - elts = sr.design.get_elements("ElArray") + elts = sr.design.get("ElArray") assert len(elts) > 0 # Keep only magnets with '& mask' @@ -134,7 +134,7 @@ def test_element_array_mask_length_mismatch_raises_for_and_and_sub( sr = accelerator_from_fragments(*sr_configuration_fragments) sr.design.get_lattice().disable_6d() - elts = sr.design.get_elements("ElArray") + elts = sr.design.get("ElArray") assert len(elts) > 0 bad_mask = [True] * (len(elts) - 1) @@ -159,7 +159,7 @@ def test_mask_by_type_returns_correct_boolean_mask( sr = accelerator_from_fragments(*sr_configuration_fragments) sr.design.get_lattice().disable_6d() - elts = sr.design.get_elements("ElArray") + elts = sr.design.get("ElArray") mask = elts.mask_by_type(Magnet) assert isinstance(mask, list) @@ -182,7 +182,7 @@ def test_filter_by_type_returns_autotyped_array(install_test_package, accelerato sr = accelerator_from_fragments(*sr_configuration_fragments) sr.design.get_lattice().disable_6d() - elts = sr.design.get_elements("ElArray") + elts = sr.design.get("ElArray") filtered = elts.of_type(Magnet) if len(filtered) == 0: diff --git a/tests/common/test_element_holder_collection.py b/tests/common/test_element_holder_collection.py index 3fdfc88d..a90a1523 100644 --- a/tests/common/test_element_holder_collection.py +++ b/tests/common/test_element_holder_collection.py @@ -22,7 +22,7 @@ def test_exact_name_returns_the_element_or_raises(holder): def test_get_and_full_slice_keep_registration_order(holder): - names = [element.get_name() for element in holder.get_all_elements()] + names = [element.get_name() for element in holder.get()] assert type(holder.get()) is ElementArray assert type(holder[:]) is ElementArray @@ -79,7 +79,7 @@ def test_colons_are_part_of_names(holder): def test_magnet_subclasses_share_a_typed_array_in_either_order(holder): horizontal = holder.magnet.get("SH1A-C01-H") vertical = holder.magnet.get("SH1A-C01-V") - start = holder.get_all_elements().index(horizontal) + start = holder.get().index(horizontal) assert type(horizontal) is not type(vertical) assert isinstance(holder["SH1A-C01-[HV]"], MagnetArray) @@ -97,7 +97,7 @@ def test_mixed_selection_returns_a_generic_array(holder): def test_indices_and_slices_follow_insertion_order(holder): - registered = holder.get_all_elements() + registered = holder.get() assert holder[0] is registered[0] assert holder[-1] is registered[-1] @@ -119,7 +119,7 @@ def test_empty_holder_returns_empty_collections(ebs_lattice_file): def test_invalid_indices_and_keys_raise_clear_errors(holder): - size = len(holder.get_all_elements()) + size = len(holder.get()) with pytest.raises(IndexError): holder[size] @@ -132,14 +132,21 @@ def test_invalid_indices_and_keys_raise_clear_errors(holder): def test_selection_intersects_with_a_configured_family(holder): - selected = holder["SH1A-C0?-H"] & holder.get_elements("ElArray") + selected = holder["SH1A-C0?-H"] & holder.get("ElArray") assert isinstance(selected, MagnetArray) assert selected.names() == ["SH1A-C02-H"] - assert holder.get_element("SH1A-C02-H") is holder["SH1A-C02-H"] - assert holder.get_all_elements() == list(holder.get()) + + +def test_get_resolves_a_named_array_regardless_of_its_concrete_family(holder): + """`get(name)` must not be limited to generic element arrays, unlike the old `get_elements`.""" + assert isinstance(holder.get("HCORR"), MagnetArray) + assert holder.get("HCORR").names() == ["SH1A-C01-H", "SH1A-C02-H"] + assert isinstance(holder.get("BPMS"), BPMArray) + assert holder.get("BPMS").names() == ["BPM_C04-01", "BPM_C04-02"] + assert holder.get("ElArray").names() == ["BPM_C04-01", "BPM_C04-02", "SH1A-C01-V", "SH1A-C02-H"] with pytest.raises(PyAMLException): - holder.get_element("UNKNOWN") + holder.get("UNKNOWN") def test_existing_array_field_filters_still_work(holder): @@ -204,7 +211,7 @@ def test_disjoint_regex_and_list_selections_produce_a_plain_list(holder): def test_selection_combined_with_a_configured_family_via_regex_and_list(holder): """`re:` and list-of-patterns selections intersect with a configured family, same as wildcards.""" - el_array = holder.get_elements("ElArray") # BPM_C04-01, BPM_C04-02, SH1A-C01-V, SH1A-C02-H + el_array = holder.get("ElArray") # BPM_C04-01, BPM_C04-02, SH1A-C01-V, SH1A-C02-H by_regex = holder["re:^SH1A-C0[12]-H$"] & el_array assert isinstance(by_regex, MagnetArray) @@ -272,7 +279,7 @@ def test_getitem_supports_an_exclusion_in_a_list(holder): def test_getitem_lone_exclusion_pattern_means_everything_except(holder): - all_names = holder.get_all_elements() + all_names = holder.get() selected = holder["~QF1A-C01"] assert "QF1A-C01" not in selected.names() diff --git a/tests/diagnostics/test_diagnostic_accessors.py b/tests/diagnostics/test_diagnostic_accessors.py index b4c6f58b..0046d975 100644 --- a/tests/diagnostics/test_diagnostic_accessors.py +++ b/tests/diagnostics/test_diagnostic_accessors.py @@ -14,7 +14,7 @@ def test_diagnostic_get_returns_named_monitor(): include_locations=False, ).design - assert design.diagnostic.get("BETATRON_TUNE") is design.get_betatron_tune_monitor("BETATRON_TUNE") + assert design.diagnostic.get("BETATRON_TUNE") is design._DIAG["BETATRON_TUNE"] def test_diagnostic_get_with_no_name_returns_all_configured_diagnostics(): @@ -25,7 +25,7 @@ def test_diagnostic_get_with_no_name_returns_all_configured_diagnostics(): ).design all_diagnostics = design.diagnostic.get() - assert design.get_betatron_tune_monitor("BETATRON_TUNE") in all_diagnostics + assert design.diagnostic.get("BETATRON_TUNE") in all_diagnostics def test_diagnostic_betatron_tune_returns_default_monitor(): @@ -35,7 +35,7 @@ def test_diagnostic_betatron_tune_returns_default_monitor(): include_locations=False, ).design - assert design.diagnostic.betatron_tune is design.get_betatron_tune_monitor("BETATRON_TUNE") + assert design.diagnostic.betatron_tune is design.diagnostic.get("BETATRON_TUNE") def test_diagnostic_raises_when_default_missing(ebs_lattice_file): @@ -128,7 +128,7 @@ def test_diagnostic_getitem_list_selection_ignores_request_order(): ignore_external=True, include_locations=False, ).design - spare_monitor = copy.copy(design.get_betatron_tune_monitor("BETATRON_TUNE")) + spare_monitor = copy.copy(design.diagnostic.get("BETATRON_TUNE")) spare_monitor._name = "SPARE_BETATRON_TUNE" design._DIAG["SPARE_BETATRON_TUNE"] = spare_monitor diff --git a/tests/integration/test_dt4acc_twin_smoke.py b/tests/integration/test_dt4acc_twin_smoke.py index 44220e09..8bb2c634 100644 --- a/tests/integration/test_dt4acc_twin_smoke.py +++ b/tests/integration/test_dt4acc_twin_smoke.py @@ -135,8 +135,8 @@ def deactivated_test_orbit_correction(config_key: str): accelerator = _build_accelerator(config_key) control_mode = accelerator.live bpms = control_mode.diagnostic.bpms.get("bpms") - orbit_response_matrix = control_mode.get_orm_tuning("DEFAULT_ORBIT_RESPONSE_MATRIX") - orbit_correction = control_mode.get_orbit_tuning("DEFAULT_ORBIT_CORRECTION") + orbit_response_matrix = control_mode.tool.get("DEFAULT_ORBIT_RESPONSE_MATRIX") + orbit_correction = control_mode.tool.get("DEFAULT_ORBIT_CORRECTION") orbit_response_matrix.measure() ormdata = orbit_response_matrix.get() orbit_response_matrix.save("orm.json") diff --git a/tests/magnet/test_serialized_magnets.py b/tests/magnet/test_serialized_magnets.py index 1e95ca70..b362f7b7 100644 --- a/tests/magnet/test_serialized_magnets.py +++ b/tests/magnet/test_serialized_magnets.py @@ -27,11 +27,11 @@ def test_config_load(sr_file): sr: Accelerator = Accelerator.load(sr_file, include_locations=False, ignore_external=True) assert sr is not None magnets = [ - sr.design.get_element("QF8B-C04"), - sr.design.get_element("QF8B-C04"), - sr.design.get_element("QD5D-C04"), - sr.design.get_element("QF6D-C04"), - sr.design.get_element("QF4D-C04"), + sr.design["QF8B-C04"], + sr.design["QF8B-C04"], + sr.design["QD5D-C04"], + sr.design["QF6D-C04"], + sr.design["QF4D-C04"], ] assert None not in [magnets] @@ -115,7 +115,7 @@ def test_tune(sr_file): assert len(m.get_magnets()) == m.get_nb_magnets() quadForTuneDesign = sr.design.serialized_magnets.get("QForTune") - tune_monitor = sr.design.get_betatron_tune_monitor("BETATRON_TUNE") + tune_monitor = sr.design.diagnostic.get("BETATRON_TUNE") # Build tune response matrix tunemat = np.zeros((len(quadForTuneDesign), 2)) diff --git a/tests/test_accelerator_load.py b/tests/test_accelerator_load.py index 0bc649ce..aa5df3ef 100644 --- a/tests/test_accelerator_load.py +++ b/tests/test_accelerator_load.py @@ -9,10 +9,10 @@ def test_peer(): sr = Accelerator.load("tests/config/tune_monitor.yaml") - tm = sr.design.get_betatron_tune_monitor("BETATRON_TUNE") + tm = sr.design.diagnostic.get("BETATRON_TUNE") assert isinstance(tm.peer.peer, Accelerator) assert isinstance(tm.peer, ElementHolder) - tm = sr.live.get_betatron_tune_monitor("BETATRON_TUNE") + tm = sr.live.diagnostic.get("BETATRON_TUNE") assert isinstance(tm.peer.peer, Accelerator) assert isinstance(tm.peer, ElementHolder) @@ -160,5 +160,5 @@ def test_config_dict(): assert sr.live.dconfig()["prefix"] == "VA:" assert sr.live.dconfig()["info"]["param1"] == "Param1 value" assert sr.live.dconfig()["info"]["param2"] == 12345.0 - assert isinstance(sr.live.get_element("MY_ELEMENT"), MyElement) - assert sr.live.get_element("MY_ELEMENT")._cfg.device_h == "TUNEZR:rdH" + assert isinstance(sr.live["MY_ELEMENT"], MyElement) + assert sr.live["MY_ELEMENT"]._cfg.device_h == "TUNEZR:rdH" diff --git a/tests/test_load_conf_with_code.py b/tests/test_load_conf_with_code.py index 25a98a8d..41771dbf 100644 --- a/tests/test_load_conf_with_code.py +++ b/tests/test_load_conf_with_code.py @@ -14,7 +14,7 @@ def test_load_conf_with_code(): assert sr.live[bpms[0].get_name()] is bpms[0] assert sr.live["BPM*"].names() == bpms.names() - assert sr.live[:].names() == [element.get_name() for element in sr.live.get_all_elements()] + assert sr.live[:].names() == [element.get_name() for element in sr.live.get()] assert sr.design["BPM*"].names() == sr.design.diagnostic.bpms.get("BPM").names() assert sr.live.diagnostic.bpms.BPM is bpms diff --git a/tests/tuning_tools/test_tool_accessors.py b/tests/tuning_tools/test_tool_accessors.py index a5dd2500..1b064490 100644 --- a/tests/tuning_tools/test_tool_accessors.py +++ b/tests/tuning_tools/test_tool_accessors.py @@ -13,7 +13,7 @@ def test_tuning_tools_expose_configured_elements(): ) design = sr.design - tune_monitor = design.get_betatron_tune_monitor("BETATRON_TUNE") + tune_monitor = design.diagnostic.get("BETATRON_TUNE") quadrupoles = design.magnets.get("QForTune") assert design.tool.tune.tune_monitor is tune_monitor assert design.tool.tune.quadrupoles is quadrupoles diff --git a/tests/tuning_tools/test_tune.py b/tests/tuning_tools/test_tune.py index a114230c..fe23a48e 100644 --- a/tests/tuning_tools/test_tune.py +++ b/tests/tuning_tools/test_tune.py @@ -14,7 +14,7 @@ def test_tune(): sr.design.get_lattice().disable_6d() quadForTuneDesign = sr.design.magnets.get("QForTune") - tune_monitor = sr.design.get_betatron_tune_monitor("BETATRON_TUNE") + tune_monitor = sr.design.diagnostic.get("BETATRON_TUNE") # Build tune response matrix tune = tune_monitor.tune.get() print(tune) diff --git a/tests/tuning_tools/test_tune_hardware.py b/tests/tuning_tools/test_tune_hardware.py index 8e83197d..e48ce265 100644 --- a/tests/tuning_tools/test_tune_hardware.py +++ b/tests/tuning_tools/test_tune_hardware.py @@ -12,7 +12,7 @@ def test_tune(): # Build tune response matrix (hardware units) - tune_monitor = sr.design.get_betatron_tune_monitor("BETATRON_TUNE") + tune_monitor = sr.design.diagnostic.get("BETATRON_TUNE") tune = tune_monitor.tune.get() print(tune) tunemat = np.zeros((len(quadForTuneDesign), 2)) diff --git a/tests/tuning_tools/test_tune_monitor.py b/tests/tuning_tools/test_tune_monitor.py index 893f096e..66694c37 100644 --- a/tests/tuning_tools/test_tune_monitor.py +++ b/tests/tuning_tools/test_tune_monitor.py @@ -11,7 +11,7 @@ def test_simulator_tune_monitor( ignore_external=True, ) sr.design.get_lattice().disable_6d() - tune_monitor = sr.design.get_betatron_tune_monitor("BETATRON_TUNE") + tune_monitor = sr.design.diagnostic.get("BETATRON_TUNE") assert tune_monitor.tune.get()[0] == sr.design.get_lattice().get_tune()[0] assert tune_monitor.tune.get()[1] == sr.design.get_lattice().get_tune()[1] assert np.abs(tune_monitor.frequency.get()[0] - 56834.22592393) < 1e-6 @@ -31,6 +31,6 @@ def test_controlsystem_tune_monitor( sr = accelerator_from_fragments( *tune_monitor_configuration_fragments, ) - tune_monitor = sr.live.get_betatron_tune_monitor("BETATRON_TUNE") + tune_monitor = sr.live.diagnostic.get("BETATRON_TUNE") assert tune_monitor.tune.get()[0] == 0.0 assert tune_monitor.tune.get()[1] == 0.0 From 5ec15ea52d3f21b10e96839fb35577aea9380faa Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Thu, 1 Oct 2026 17:40:34 +0200 Subject: [PATCH 39/49] Remove ElementHolder.get_chromaticity_monitor/get_bba, superseded by tool.get(name) --- .../02-chromaticity_measurement.ipynb | 2 +- .../use_cases/02-chromaticity_measurement.py | 2 +- pyaml/common/holders/element_holder.py | 43 ------------------- pyaml/tuning_tools/bba.py | 2 +- pyaml/tuning_tools/bba2.py | 2 +- pyaml/tuning_tools/chromaticity.py | 2 +- .../chromaticity_response_matrix.py | 2 +- tests/integration/test_dt4acc_twin_smoke.py | 2 +- tests/tuning_tools/test_bba.py | 4 +- .../tuning_tools/test_chromaticity_monitor.py | 4 +- tests/tuning_tools/test_tool_accessors.py | 6 +-- 11 files changed, 14 insertions(+), 57 deletions(-) diff --git a/examples/use_cases/02-chromaticity_measurement.ipynb b/examples/use_cases/02-chromaticity_measurement.ipynb index 9ac1afca..05ea57a2 100644 --- a/examples/use_cases/02-chromaticity_measurement.ipynb +++ b/examples/use_cases/02-chromaticity_measurement.ipynb @@ -267,7 +267,7 @@ } ], "source": [ - "chroma_monitor = SR.get_chromaticity_monitor(\"CHROMATICITY_MONITOR\")\n", + "chroma_monitor = SR.tool.get(\"CHROMATICITY_MONITOR\")\n", "\n", "chroma_monitor.measure(\n", " callback=chroma_callback,\n", diff --git a/examples/use_cases/02-chromaticity_measurement.py b/examples/use_cases/02-chromaticity_measurement.py index 966b23ef..320aa0b9 100644 --- a/examples/use_cases/02-chromaticity_measurement.py +++ b/examples/use_cases/02-chromaticity_measurement.py @@ -109,7 +109,7 @@ def chroma_callback(action: int, cb_data: dict): # In[17]: -chroma_monitor = SR.get_chromaticity_monitor("CHROMATICITY_MONITOR") +chroma_monitor = SR.tool.get("CHROMATICITY_MONITOR") chroma_monitor.measure( callback=chroma_callback, diff --git a/pyaml/common/holders/element_holder.py b/pyaml/common/holders/element_holder.py index 65291b2a..f5880dd2 100644 --- a/pyaml/common/holders/element_holder.py +++ b/pyaml/common/holders/element_holder.py @@ -11,7 +11,6 @@ from ...magnet.serialized_magnet import SerializedMagnets from ...rf.rf_plant import RFPlant from ...rf.rf_transmitter import RFTransmitter -from ...tuning_tools.chromaticity_monitor import ChromaticityMonitor from ..abstract_aggregator import ScalarAggregator from ..element import Element from ..exception import PyAMLException @@ -31,7 +30,6 @@ if TYPE_CHECKING: from ...accelerator import Accelerator from ...configuration.unbound_element import UnboundElement - from ...tuning_tools.bba import BBA from ...tuning_tools.measurement_tool import MeasurementTool from ...tuning_tools.tuning_tool import TuningTool @@ -84,10 +82,6 @@ class ElementHolder(metaclass=ABCMeta): Add a betatron tune monitor to the diagnostics store. add_tool(tool) Add a tuning or measurement tool to the tool store. - get_chromaticity_monitor(name) - Return a named chromaticity monitor. - get_bba(name) - Return a named beam-based alignment tool. """ def __init__(self): @@ -583,43 +577,6 @@ def add_tool(self, tool: Element): """ self._add(self._TOOLS, tool) - # ---- Chromaticity ------------------------------------------------- - - def get_chromaticity_monitor(self, name: str) -> ChromaticityMonitor: - """ - Return a named chromaticity monitor. - - Parameters - ---------- - name : str - Name of the chromaticity monitor to look up, as declared in the configuration. - - Returns - ------- - ChromaticityMonitor - The chromaticity monitor registered under ``name``. - """ - obj = self._get("Chromaticity monitor", name, self._TOOLS) - return obj - - # ---- BBA -------------------------------------------------------- - - def get_bba(self, name: str) -> "BBA": - """ - Return a named beam-based alignment tool. - - Parameters - ---------- - name : str - Name of the beam-based alignment tool to look up, as declared in the configuration. - - Returns - ------- - 'BBA' - The beam-based alignment tool registered under ``name``. - """ - return self._get("BBA tool", name, self._TOOLS) - def _get_array(self, name: str): """ Generic array resolver used by YellowPages. diff --git a/pyaml/tuning_tools/bba.py b/pyaml/tuning_tools/bba.py index d5cc32df..05eb9b8e 100644 --- a/pyaml/tuning_tools/bba.py +++ b/pyaml/tuning_tools/bba.py @@ -206,7 +206,7 @@ def measure( sr = Accelerator.load("tests/config/EBSOrbit.yaml") SR = sr.design - bba = SR.get_bba("BBA-BPM_C04-04") + bba = SR.tool.get("BBA-BPM_C04-04") # Add a misalignement SR.get_bpm("BPM_C04-04").offset.set([20e-6,-15e-6]) diff --git a/pyaml/tuning_tools/bba2.py b/pyaml/tuning_tools/bba2.py index 5d6e5f2b..1e34d80d 100644 --- a/pyaml/tuning_tools/bba2.py +++ b/pyaml/tuning_tools/bba2.py @@ -585,7 +585,7 @@ def measure( sr = Accelerator.load("tests/config/EBSOrbit.yaml") SR = sr.live - bba = SR.get_bba("BBA2-BPM_C04-04") + bba = SR.tool.get("BBA2-BPM_C04-04") # Add a misalignement SR.get_bpm("BPM_C04-04").offset.set([200e-6,-150e-6]) diff --git a/pyaml/tuning_tools/chromaticity.py b/pyaml/tuning_tools/chromaticity.py index 94d475ee..d43c4672 100644 --- a/pyaml/tuning_tools/chromaticity.py +++ b/pyaml/tuning_tools/chromaticity.py @@ -135,7 +135,7 @@ def load(self, load_path: Path): def chromaticity_monitor(self) -> "ChromaticityMonitor": """Return the chromaticity monitor used for readback.""" self.check_peer() - return self.peer.get_chromaticity_monitor(self._chromaticity_monitor_name) + return self.peer.tool.get(self._chromaticity_monitor_name) @property def sextupoles(self) -> "MagnetArray": diff --git a/pyaml/tuning_tools/chromaticity_response_matrix.py b/pyaml/tuning_tools/chromaticity_response_matrix.py index 4d739e0e..23d712ed 100644 --- a/pyaml/tuning_tools/chromaticity_response_matrix.py +++ b/pyaml/tuning_tools/chromaticity_response_matrix.py @@ -115,7 +115,7 @@ def sextupoles(self) -> "MagnetArray": def chromaticity_monitor(self) -> "ChromaticityMonitor": """Return the chromaticity monitor used for the measurement.""" self.check_peer() - return self.peer.get_chromaticity_monitor(self.chromaticity_name) + return self.peer.tool.get(self.chromaticity_name) def measure( self, diff --git a/tests/integration/test_dt4acc_twin_smoke.py b/tests/integration/test_dt4acc_twin_smoke.py index 8bb2c634..1e891627 100644 --- a/tests/integration/test_dt4acc_twin_smoke.py +++ b/tests/integration/test_dt4acc_twin_smoke.py @@ -179,7 +179,7 @@ def deactivated_test_chromaticity_measurement(config_key: str): accelerator = _build_accelerator(config_key) control_mode = accelerator.live - chromaticity_measurement = control_mode.get_chromaticity_monitor("DEFAULT_CHROMATICITY_MEASUREMENT") + chromaticity_measurement = control_mode.tool.get("DEFAULT_CHROMATICITY_MEASUREMENT") def chroma_callback(action: int, cb_data: dict): if action == Action.MEASURE: diff --git a/tests/tuning_tools/test_bba.py b/tests/tuning_tools/test_bba.py index f4247103..22f60c14 100644 --- a/tests/tuning_tools/test_bba.py +++ b/tests/tuning_tools/test_bba.py @@ -12,13 +12,13 @@ def test_bba(): SR.diagnostic.bpm.get("BPM_C04-04").offset.set([20e-6, -15e-6]) # BBA (standard bow tie, model independant) - bba = SR.get_bba("BBA-BPM_C04-04") + bba = SR.tool.get("BBA-BPM_C04-04") bba.measure() assert np.abs(bba.h_offset() - 20e-6) < 1e-6 assert np.abs(bba.v_offset() + 15e-6) < 1e-6 # BBA (model dependant method) - bba = SR.get_bba("BBA2-BPM_C04-04") + bba = SR.tool.get("BBA2-BPM_C04-04") bba.minicycle_sleep_time = 0 bba.measure() assert np.abs(bba.h_offset() - 20e-6) < 1e-6 diff --git a/tests/tuning_tools/test_chromaticity_monitor.py b/tests/tuning_tools/test_chromaticity_monitor.py index 3f7671d9..4f9c7e11 100644 --- a/tests/tuning_tools/test_chromaticity_monitor.py +++ b/tests/tuning_tools/test_chromaticity_monitor.py @@ -13,7 +13,7 @@ def test_simulator_chromaticity_monitor(): sr: Accelerator = Accelerator.load("tests/config/EBSOrbit.yaml", ignore_external=True) sr.design.get_lattice().enable_6d() chromaAT = sr.design.get_lattice().get_chrom()[:-1] - chromaticity_monitor = sr.design.get_chromaticity_monitor("CHROMATICITY_MONITOR") + chromaticity_monitor = sr.design.tool.get("CHROMATICITY_MONITOR") assert chromaticity_monitor.chromaticity.unit() == "1" assert chromaticity_monitor.dispersion.unit() == "m" chromaticity_monitor.measure(fit_dispersion=True, callback=callback) @@ -43,7 +43,7 @@ def test_simulator_chromaticity_monitor(): ) def test_controlsystem_chromaticity_monitor(install_test_package): sr: Accelerator = Accelerator.load("tests/config/EBSOrbit.yaml") - chromaticity_monitor = sr.live.get_chromaticity_monitor("CHROMATICITY_MONITOR") + chromaticity_monitor = sr.live.tool.get("CHROMATICITY_MONITOR") assert chromaticity_monitor.chromaticity.get() is None chromaticity_monitor.measure( do_plot=False, diff --git a/tests/tuning_tools/test_tool_accessors.py b/tests/tuning_tools/test_tool_accessors.py index 1b064490..f582040a 100644 --- a/tests/tuning_tools/test_tool_accessors.py +++ b/tests/tuning_tools/test_tool_accessors.py @@ -20,7 +20,7 @@ def test_tuning_tools_expose_configured_elements(): assert design.tool.trm.tune_monitor is tune_monitor assert design.tool.trm.quadrupoles is quadrupoles - chromaticity_monitor = design.get_chromaticity_monitor("CHROMATICITY_MONITOR") + chromaticity_monitor = design.tool.get("CHROMATICITY_MONITOR") sextupoles = design.magnets.get("Sext") assert design.tool.chromaticity.chromaticity_monitor is chromaticity_monitor assert design.tool.chromaticity.sextupoles is sextupoles @@ -47,14 +47,14 @@ def test_tuning_tools_expose_configured_elements(): assert design.tool.orm.vcorrectors is vcorrectors for name in ("BBA-BPM_C04-04", "BBA2-BPM_C04-04"): - bba = design.get_bba(name) + bba = design.tool.get(name) assert bba.bpms is bpms assert bba.bpm is design.diagnostic.bpm.get("BPM_C04-04") assert bba.hcorrector is design.magnet.get("SF2E-C02-H") assert bba.vcorrector is design.magnet.get("SD1A-C26-V") assert bba.quadrupole is design.magnet.get("QF6B-C04") - assert design.get_bba("BBA2-BPM_C04-04").tune_correction is design.tool.tune + assert design.tool.get("BBA2-BPM_C04-04").tune_correction is design.tool.tune def test_tool_get_returns_named_tool(): From b93d97d45fffd0ddfcb4d163ad371ab3a04f0663 Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Mon, 5 Oct 2026 09:51:41 +0200 Subject: [PATCH 40/49] Moving BPMs to diagnostics --- .../ControlSystem/tango/config.yaml | 2 +- examples/use_cases/config/bessy2/bessy2.yaml | 246 +++---- examples/use_cases/config/esrf/esrf.yaml | 640 +++++++++--------- .../use_cases/config/soleil_ii/devices.yaml | 360 +++++----- pyaml/apidoc/gen_api.py | 2 +- pyaml/arrays/bpm_array.py | 9 +- pyaml/arrays/element_array.py | 3 +- pyaml/bpm/__init__.py | 0 pyaml/common/holders/element_holder.py | 3 +- pyaml/common/holders/sub_holders.py | 3 +- pyaml/control/controlsystem.py | 3 +- pyaml/diagnostics/bpm/__init__.py | 3 + pyaml/{ => diagnostics}/bpm/bpm.py | 12 +- pyaml/lattice/simulator.py | 3 +- pyaml/tuning_tools/bba.py | 3 +- pyaml/tuning_tools/bba2.py | 3 +- .../common/test_element_holder_collection.py | 2 +- tests/common/test_errors.py | 14 +- tests/config/EBSOrbit.yaml | 640 +++++++++--------- tests/config/bad_conf_duplicate_2.yaml | 6 +- tests/config/bad_conf_duplicate_3.yaml | 8 +- tests/config/bpms.yaml | 8 +- tests/config/config_manager_sr_devices.yaml | 4 +- tests/config/sr.yaml | 4 +- .../test_configuration_manager.py | 4 +- tests/{ => diagnostics}/bpm/test_bpm.py | 0 .../bpm/test_bpm_controlsystem.py | 0 tests/test_catalogs.py | 6 +- 28 files changed, 1002 insertions(+), 989 deletions(-) delete mode 100644 pyaml/bpm/__init__.py create mode 100644 pyaml/diagnostics/bpm/__init__.py rename pyaml/{ => diagnostics}/bpm/bpm.py (94%) rename tests/{ => diagnostics}/bpm/test_bpm.py (100%) rename tests/{ => diagnostics}/bpm/test_bpm_controlsystem.py (100%) diff --git a/examples/other_examples/ControlSystem/tango/config.yaml b/examples/other_examples/ControlSystem/tango/config.yaml index 22881f75..1c87c843 100644 --- a/examples/other_examples/ControlSystem/tango/config.yaml +++ b/examples/other_examples/ControlSystem/tango/config.yaml @@ -8,7 +8,7 @@ controls: name: live data_folder: /data/store devices: -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-01 x_pos: srdiag/bpm/c01-01/SA_HPosition y_pos: srdiag/bpm/c01-01/SA_VPosition diff --git a/examples/use_cases/config/bessy2/bessy2.yaml b/examples/use_cases/config/bessy2/bessy2.yaml index eb493815..8d5d1f35 100644 --- a/examples/use_cases/config/bessy2/bessy2.yaml +++ b/examples/use_cases/config/bessy2/bessy2.yaml @@ -1356,495 +1356,495 @@ devices: unit: 1/m powerconverter: (VS3P1D1R:rdbk, VS3P1D1R:set)[A] hardware_unit: A -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5D1R x_pos: ORBITCC:rdPos@0 y_pos: ORBITCC:rdPos@1 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6D1R x_pos: ORBITCC:rdPos@2 y_pos: ORBITCC:rdPos@3 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7D1R x_pos: ORBITCC:rdPos@4 y_pos: ORBITCC:rdPos@5 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1T1R x_pos: ORBITCC:rdPos@6 y_pos: ORBITCC:rdPos@7 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2T1R x_pos: ORBITCC:rdPos@8 y_pos: ORBITCC:rdPos@9 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3T1R x_pos: ORBITCC:rdPos@10 y_pos: ORBITCC:rdPos@11 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4T1R x_pos: ORBITCC:rdPos@12 y_pos: ORBITCC:rdPos@13 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ41T1R x_pos: ORBITCC:rdPos@14 y_pos: ORBITCC:rdPos@15 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5T1R x_pos: ORBITCC:rdPos@16 y_pos: ORBITCC:rdPos@17 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6T1R x_pos: ORBITCC:rdPos@18 y_pos: ORBITCC:rdPos@19 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7T1R x_pos: ORBITCC:rdPos@20 y_pos: ORBITCC:rdPos@21 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1D2R x_pos: ORBITCC:rdPos@22 y_pos: ORBITCC:rdPos@23 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2D2R x_pos: ORBITCC:rdPos@24 y_pos: ORBITCC:rdPos@25 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3D2R x_pos: ORBITCC:rdPos@26 y_pos: ORBITCC:rdPos@27 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4D2R x_pos: ORBITCC:rdPos@28 y_pos: ORBITCC:rdPos@29 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5D2R x_pos: ORBITCC:rdPos@30 y_pos: ORBITCC:rdPos@31 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6D2R x_pos: ORBITCC:rdPos@32 y_pos: ORBITCC:rdPos@33 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7D2R x_pos: ORBITCC:rdPos@34 y_pos: ORBITCC:rdPos@35 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1T2R x_pos: ORBITCC:rdPos@36 y_pos: ORBITCC:rdPos@37 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2T2R x_pos: ORBITCC:rdPos@38 y_pos: ORBITCC:rdPos@39 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3T2R x_pos: ORBITCC:rdPos@40 y_pos: ORBITCC:rdPos@41 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4T2R x_pos: ORBITCC:rdPos@42 y_pos: ORBITCC:rdPos@43 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ41T2R x_pos: ORBITCC:rdPos@44 y_pos: ORBITCC:rdPos@45 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ42T2R x_pos: ORBITCC:rdPos@46 y_pos: ORBITCC:rdPos@47 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ43T2R x_pos: ORBITCC:rdPos@48 y_pos: ORBITCC:rdPos@49 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5T2R x_pos: ORBITCC:rdPos@50 y_pos: ORBITCC:rdPos@51 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6T2R x_pos: ORBITCC:rdPos@52 y_pos: ORBITCC:rdPos@53 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7T2R x_pos: ORBITCC:rdPos@54 y_pos: ORBITCC:rdPos@55 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1D3R x_pos: ORBITCC:rdPos@56 y_pos: ORBITCC:rdPos@57 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2D3R x_pos: ORBITCC:rdPos@58 y_pos: ORBITCC:rdPos@59 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3D3R x_pos: ORBITCC:rdPos@60 y_pos: ORBITCC:rdPos@61 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4D3R x_pos: ORBITCC:rdPos@62 y_pos: ORBITCC:rdPos@63 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5D3R x_pos: ORBITCC:rdPos@64 y_pos: ORBITCC:rdPos@65 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6D3R x_pos: ORBITCC:rdPos@66 y_pos: ORBITCC:rdPos@67 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7D3R x_pos: ORBITCC:rdPos@68 y_pos: ORBITCC:rdPos@69 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1T3R x_pos: ORBITCC:rdPos@70 y_pos: ORBITCC:rdPos@71 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2T3R x_pos: ORBITCC:rdPos@72 y_pos: ORBITCC:rdPos@73 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3T3R x_pos: ORBITCC:rdPos@74 y_pos: ORBITCC:rdPos@75 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4T3R x_pos: ORBITCC:rdPos@76 y_pos: ORBITCC:rdPos@77 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5T3R x_pos: ORBITCC:rdPos@78 y_pos: ORBITCC:rdPos@79 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6T3R x_pos: ORBITCC:rdPos@80 y_pos: ORBITCC:rdPos@81 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7T3R x_pos: ORBITCC:rdPos@82 y_pos: ORBITCC:rdPos@83 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1D4R x_pos: ORBITCC:rdPos@84 y_pos: ORBITCC:rdPos@85 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2D4R x_pos: ORBITCC:rdPos@86 y_pos: ORBITCC:rdPos@87 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3D4R x_pos: ORBITCC:rdPos@88 y_pos: ORBITCC:rdPos@89 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4D4R x_pos: ORBITCC:rdPos@90 y_pos: ORBITCC:rdPos@91 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5D4R x_pos: ORBITCC:rdPos@92 y_pos: ORBITCC:rdPos@93 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6D4R x_pos: ORBITCC:rdPos@94 y_pos: ORBITCC:rdPos@95 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7D4R x_pos: ORBITCC:rdPos@96 y_pos: ORBITCC:rdPos@97 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1T4R x_pos: ORBITCC:rdPos@98 y_pos: ORBITCC:rdPos@99 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2T4R x_pos: ORBITCC:rdPos@100 y_pos: ORBITCC:rdPos@101 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3T4R x_pos: ORBITCC:rdPos@102 y_pos: ORBITCC:rdPos@103 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4T4R x_pos: ORBITCC:rdPos@104 y_pos: ORBITCC:rdPos@105 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5T4R x_pos: ORBITCC:rdPos@106 y_pos: ORBITCC:rdPos@107 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6T4R x_pos: ORBITCC:rdPos@108 y_pos: ORBITCC:rdPos@109 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7T4R x_pos: ORBITCC:rdPos@110 y_pos: ORBITCC:rdPos@111 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1D5R x_pos: ORBITCC:rdPos@112 y_pos: ORBITCC:rdPos@113 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2D5R x_pos: ORBITCC:rdPos@114 y_pos: ORBITCC:rdPos@115 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3D5R x_pos: ORBITCC:rdPos@116 y_pos: ORBITCC:rdPos@117 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4D5R x_pos: ORBITCC:rdPos@118 y_pos: ORBITCC:rdPos@119 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5D5R x_pos: ORBITCC:rdPos@120 y_pos: ORBITCC:rdPos@121 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6D5R x_pos: ORBITCC:rdPos@122 y_pos: ORBITCC:rdPos@123 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7D5R x_pos: ORBITCC:rdPos@124 y_pos: ORBITCC:rdPos@125 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1T5R x_pos: ORBITCC:rdPos@126 y_pos: ORBITCC:rdPos@127 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2T5R x_pos: ORBITCC:rdPos@128 y_pos: ORBITCC:rdPos@129 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3T5R x_pos: ORBITCC:rdPos@130 y_pos: ORBITCC:rdPos@131 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4T5R x_pos: ORBITCC:rdPos@132 y_pos: ORBITCC:rdPos@133 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5T5R x_pos: ORBITCC:rdPos@134 y_pos: ORBITCC:rdPos@135 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6T5R x_pos: ORBITCC:rdPos@136 y_pos: ORBITCC:rdPos@137 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7T5R x_pos: ORBITCC:rdPos@138 y_pos: ORBITCC:rdPos@139 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1D6R x_pos: ORBITCC:rdPos@140 y_pos: ORBITCC:rdPos@141 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2D6R x_pos: ORBITCC:rdPos@142 y_pos: ORBITCC:rdPos@143 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3D6R x_pos: ORBITCC:rdPos@144 y_pos: ORBITCC:rdPos@145 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4D6R x_pos: ORBITCC:rdPos@146 y_pos: ORBITCC:rdPos@147 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ41D6R x_pos: ORBITCC:rdPos@148 y_pos: ORBITCC:rdPos@149 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ42D6R x_pos: ORBITCC:rdPos@150 y_pos: ORBITCC:rdPos@151 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ43D6R x_pos: ORBITCC:rdPos@152 y_pos: ORBITCC:rdPos@153 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ44D6R x_pos: ORBITCC:rdPos@154 y_pos: ORBITCC:rdPos@155 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6D6R x_pos: ORBITCC:rdPos@156 y_pos: ORBITCC:rdPos@157 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7D6R x_pos: ORBITCC:rdPos@158 y_pos: ORBITCC:rdPos@159 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1T6R x_pos: ORBITCC:rdPos@160 y_pos: ORBITCC:rdPos@161 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2T6R x_pos: ORBITCC:rdPos@162 y_pos: ORBITCC:rdPos@163 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3T6R x_pos: ORBITCC:rdPos@164 y_pos: ORBITCC:rdPos@165 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4T6R x_pos: ORBITCC:rdPos@166 y_pos: ORBITCC:rdPos@167 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ41T6R x_pos: ORBITCC:rdPos@168 y_pos: ORBITCC:rdPos@169 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5T6R x_pos: ORBITCC:rdPos@170 y_pos: ORBITCC:rdPos@171 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6T6R x_pos: ORBITCC:rdPos@172 y_pos: ORBITCC:rdPos@173 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7T6R x_pos: ORBITCC:rdPos@174 y_pos: ORBITCC:rdPos@175 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1D7R x_pos: ORBITCC:rdPos@176 y_pos: ORBITCC:rdPos@177 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2D7R x_pos: ORBITCC:rdPos@178 y_pos: ORBITCC:rdPos@179 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3D7R x_pos: ORBITCC:rdPos@180 y_pos: ORBITCC:rdPos@181 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4D7R x_pos: ORBITCC:rdPos@182 y_pos: ORBITCC:rdPos@183 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5D7R x_pos: ORBITCC:rdPos@184 y_pos: ORBITCC:rdPos@185 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6D7R x_pos: ORBITCC:rdPos@186 y_pos: ORBITCC:rdPos@187 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7D7R x_pos: ORBITCC:rdPos@188 y_pos: ORBITCC:rdPos@189 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1T7R x_pos: ORBITCC:rdPos@190 y_pos: ORBITCC:rdPos@191 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2T7R x_pos: ORBITCC:rdPos@192 y_pos: ORBITCC:rdPos@193 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3T7R x_pos: ORBITCC:rdPos@194 y_pos: ORBITCC:rdPos@195 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4T7R x_pos: ORBITCC:rdPos@196 y_pos: ORBITCC:rdPos@197 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ41T7R x_pos: ORBITCC:rdPos@198 y_pos: ORBITCC:rdPos@199 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5T7R x_pos: ORBITCC:rdPos@200 y_pos: ORBITCC:rdPos@201 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6T7R x_pos: ORBITCC:rdPos@202 y_pos: ORBITCC:rdPos@203 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7T7R x_pos: ORBITCC:rdPos@204 y_pos: ORBITCC:rdPos@205 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1D8R x_pos: ORBITCC:rdPos@206 y_pos: ORBITCC:rdPos@207 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2D8R x_pos: ORBITCC:rdPos@208 y_pos: ORBITCC:rdPos@209 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3D8R x_pos: ORBITCC:rdPos@210 y_pos: ORBITCC:rdPos@211 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4D8R x_pos: ORBITCC:rdPos@212 y_pos: ORBITCC:rdPos@213 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5D8R x_pos: ORBITCC:rdPos@214 y_pos: ORBITCC:rdPos@215 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6D8R x_pos: ORBITCC:rdPos@216 y_pos: ORBITCC:rdPos@217 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7D8R x_pos: ORBITCC:rdPos@218 y_pos: ORBITCC:rdPos@219 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1T8R x_pos: ORBITCC:rdPos@220 y_pos: ORBITCC:rdPos@221 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2T8R x_pos: ORBITCC:rdPos@222 y_pos: ORBITCC:rdPos@223 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3T8R x_pos: ORBITCC:rdPos@224 y_pos: ORBITCC:rdPos@225 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4T8R x_pos: ORBITCC:rdPos@226 y_pos: ORBITCC:rdPos@227 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ5T8R x_pos: ORBITCC:rdPos@228 y_pos: ORBITCC:rdPos@229 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ6T8R x_pos: ORBITCC:rdPos@230 y_pos: ORBITCC:rdPos@231 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ7T8R x_pos: ORBITCC:rdPos@232 y_pos: ORBITCC:rdPos@233 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ1D1R x_pos: ORBITCC:rdPos@234 y_pos: ORBITCC:rdPos@235 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ2D1R x_pos: ORBITCC:rdPos@236 y_pos: ORBITCC:rdPos@237 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ3D1R x_pos: ORBITCC:rdPos@238 y_pos: ORBITCC:rdPos@239 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ4D1R x_pos: ORBITCC:rdPos@240 y_pos: ORBITCC:rdPos@241 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ41D1R x_pos: ORBITCC:rdPos@242 y_pos: ORBITCC:rdPos@243 -- class: pyaml.bpm.bpm.BPM +- class: pyaml.diagnostics.bpm.bpm.BPM name: BPMZ42D1R x_pos: ORBITCC:rdPos@244 y_pos: ORBITCC:rdPos@245 diff --git a/examples/use_cases/config/esrf/esrf.yaml b/examples/use_cases/config/esrf/esrf.yaml index a3bdba75..88899f73 100644 --- a/examples/use_cases/config/esrf/esrf.yaml +++ b/examples/use_cases/config/esrf/esrf.yaml @@ -5713,1285 +5713,1285 @@ devices: - srmag/ps-corr-sh3/c03-e-ch3/current - srmag/ps-corr-sh3/c03-e-ch5/current - srmag/ps-corr-sh3/c03-e-ch6/current -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-01 x_pos: srdiag/bpm/c04-01/SA_HPosition y_pos: srdiag/bpm/c04-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-02 x_pos: srdiag/bpm/c04-02/SA_HPosition y_pos: srdiag/bpm/c04-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-03 x_pos: srdiag/bpm/c04-03/SA_HPosition y_pos: srdiag/bpm/c04-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-04 x_pos: srdiag/bpm/c04-04/SA_HPosition y_pos: srdiag/bpm/c04-04/SA_VPosition x_offset: srdiag/bpm/c04-04/HOffset y_offset: srdiag/bpm/c04-04/VOffset -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-05 x_pos: srdiag/bpm/c04-05/SA_HPosition y_pos: srdiag/bpm/c04-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-06 x_pos: srdiag/bpm/c04-06/SA_HPosition y_pos: srdiag/bpm/c04-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-07 x_pos: srdiag/bpm/c04-07/SA_HPosition y_pos: srdiag/bpm/c04-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-08 x_pos: srdiag/bpm/c04-08/SA_HPosition y_pos: srdiag/bpm/c04-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-09 x_pos: srdiag/bpm/c04-09/SA_HPosition y_pos: srdiag/bpm/c04-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-10 x_pos: srdiag/bpm/c04-10/SA_HPosition y_pos: srdiag/bpm/c04-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-01 x_pos: srdiag/bpm/c05-01/SA_HPosition y_pos: srdiag/bpm/c05-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-02 x_pos: srdiag/bpm/c05-02/SA_HPosition y_pos: srdiag/bpm/c05-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-03 x_pos: srdiag/bpm/c05-03/SA_HPosition y_pos: srdiag/bpm/c05-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-04 x_pos: srdiag/bpm/c05-04/SA_HPosition y_pos: srdiag/bpm/c05-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-05 x_pos: srdiag/bpm/c05-05/SA_HPosition y_pos: srdiag/bpm/c05-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-06 x_pos: srdiag/bpm/c05-06/SA_HPosition y_pos: srdiag/bpm/c05-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-07 x_pos: srdiag/bpm/c05-07/SA_HPosition y_pos: srdiag/bpm/c05-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-08 x_pos: srdiag/bpm/c05-08/SA_HPosition y_pos: srdiag/bpm/c05-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-09 x_pos: srdiag/bpm/c05-09/SA_HPosition y_pos: srdiag/bpm/c05-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-10 x_pos: srdiag/bpm/c05-10/SA_HPosition y_pos: srdiag/bpm/c05-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-01 x_pos: srdiag/bpm/c06-01/SA_HPosition y_pos: srdiag/bpm/c06-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-02 x_pos: srdiag/bpm/c06-02/SA_HPosition y_pos: srdiag/bpm/c06-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-03 x_pos: srdiag/bpm/c06-03/SA_HPosition y_pos: srdiag/bpm/c06-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-04 x_pos: srdiag/bpm/c06-04/SA_HPosition y_pos: srdiag/bpm/c06-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-05 x_pos: srdiag/bpm/c06-05/SA_HPosition y_pos: srdiag/bpm/c06-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-06 x_pos: srdiag/bpm/c06-06/SA_HPosition y_pos: srdiag/bpm/c06-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-07 x_pos: srdiag/bpm/c06-07/SA_HPosition y_pos: srdiag/bpm/c06-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-08 x_pos: srdiag/bpm/c06-08/SA_HPosition y_pos: srdiag/bpm/c06-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-09 x_pos: srdiag/bpm/c06-09/SA_HPosition y_pos: srdiag/bpm/c06-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-10 x_pos: srdiag/bpm/c06-10/SA_HPosition y_pos: srdiag/bpm/c06-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-01 x_pos: srdiag/bpm/c07-01/SA_HPosition y_pos: srdiag/bpm/c07-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-02 x_pos: srdiag/bpm/c07-02/SA_HPosition y_pos: srdiag/bpm/c07-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-03 x_pos: srdiag/bpm/c07-03/SA_HPosition y_pos: srdiag/bpm/c07-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-04 x_pos: srdiag/bpm/c07-04/SA_HPosition y_pos: srdiag/bpm/c07-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-05 x_pos: srdiag/bpm/c07-05/SA_HPosition y_pos: srdiag/bpm/c07-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-06 x_pos: srdiag/bpm/c07-06/SA_HPosition y_pos: srdiag/bpm/c07-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-07 x_pos: srdiag/bpm/c07-07/SA_HPosition y_pos: srdiag/bpm/c07-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-08 x_pos: srdiag/bpm/c07-08/SA_HPosition y_pos: srdiag/bpm/c07-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-09 x_pos: srdiag/bpm/c07-09/SA_HPosition y_pos: srdiag/bpm/c07-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-10 x_pos: srdiag/bpm/c07-10/SA_HPosition y_pos: srdiag/bpm/c07-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-01 x_pos: srdiag/bpm/c08-01/SA_HPosition y_pos: srdiag/bpm/c08-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-02 x_pos: srdiag/bpm/c08-02/SA_HPosition y_pos: srdiag/bpm/c08-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-03 x_pos: srdiag/bpm/c08-03/SA_HPosition y_pos: srdiag/bpm/c08-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-04 x_pos: srdiag/bpm/c08-04/SA_HPosition y_pos: srdiag/bpm/c08-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-05 x_pos: srdiag/bpm/c08-05/SA_HPosition y_pos: srdiag/bpm/c08-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-06 x_pos: srdiag/bpm/c08-06/SA_HPosition y_pos: srdiag/bpm/c08-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-07 x_pos: srdiag/bpm/c08-07/SA_HPosition y_pos: srdiag/bpm/c08-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-08 x_pos: srdiag/bpm/c08-08/SA_HPosition y_pos: srdiag/bpm/c08-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-09 x_pos: srdiag/bpm/c08-09/SA_HPosition y_pos: srdiag/bpm/c08-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-10 x_pos: srdiag/bpm/c08-10/SA_HPosition y_pos: srdiag/bpm/c08-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-01 x_pos: srdiag/bpm/c09-01/SA_HPosition y_pos: srdiag/bpm/c09-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-02 x_pos: srdiag/bpm/c09-02/SA_HPosition y_pos: srdiag/bpm/c09-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-03 x_pos: srdiag/bpm/c09-03/SA_HPosition y_pos: srdiag/bpm/c09-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-04 x_pos: srdiag/bpm/c09-04/SA_HPosition y_pos: srdiag/bpm/c09-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-05 x_pos: srdiag/bpm/c09-05/SA_HPosition y_pos: srdiag/bpm/c09-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-06 x_pos: srdiag/bpm/c09-06/SA_HPosition y_pos: srdiag/bpm/c09-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-07 x_pos: srdiag/bpm/c09-07/SA_HPosition y_pos: srdiag/bpm/c09-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-08 x_pos: srdiag/bpm/c09-08/SA_HPosition y_pos: srdiag/bpm/c09-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-09 x_pos: srdiag/bpm/c09-09/SA_HPosition y_pos: srdiag/bpm/c09-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-10 x_pos: srdiag/bpm/c09-10/SA_HPosition y_pos: srdiag/bpm/c09-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-01 x_pos: srdiag/bpm/c10-01/SA_HPosition y_pos: srdiag/bpm/c10-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-02 x_pos: srdiag/bpm/c10-02/SA_HPosition y_pos: srdiag/bpm/c10-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-03 x_pos: srdiag/bpm/c10-03/SA_HPosition y_pos: srdiag/bpm/c10-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-04 x_pos: srdiag/bpm/c10-04/SA_HPosition y_pos: srdiag/bpm/c10-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-05 x_pos: srdiag/bpm/c10-05/SA_HPosition y_pos: srdiag/bpm/c10-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-06 x_pos: srdiag/bpm/c10-06/SA_HPosition y_pos: srdiag/bpm/c10-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-07 x_pos: srdiag/bpm/c10-07/SA_HPosition y_pos: srdiag/bpm/c10-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-08 x_pos: srdiag/bpm/c10-08/SA_HPosition y_pos: srdiag/bpm/c10-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-09 x_pos: srdiag/bpm/c10-09/SA_HPosition y_pos: srdiag/bpm/c10-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-10 x_pos: srdiag/bpm/c10-10/SA_HPosition y_pos: srdiag/bpm/c10-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-01 x_pos: srdiag/bpm/c11-01/SA_HPosition y_pos: srdiag/bpm/c11-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-02 x_pos: srdiag/bpm/c11-02/SA_HPosition y_pos: srdiag/bpm/c11-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-03 x_pos: srdiag/bpm/c11-03/SA_HPosition y_pos: srdiag/bpm/c11-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-04 x_pos: srdiag/bpm/c11-04/SA_HPosition y_pos: srdiag/bpm/c11-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-05 x_pos: srdiag/bpm/c11-05/SA_HPosition y_pos: srdiag/bpm/c11-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-06 x_pos: srdiag/bpm/c11-06/SA_HPosition y_pos: srdiag/bpm/c11-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-07 x_pos: srdiag/bpm/c11-07/SA_HPosition y_pos: srdiag/bpm/c11-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-08 x_pos: srdiag/bpm/c11-08/SA_HPosition y_pos: srdiag/bpm/c11-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-09 x_pos: srdiag/bpm/c11-09/SA_HPosition y_pos: srdiag/bpm/c11-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-10 x_pos: srdiag/bpm/c11-10/SA_HPosition y_pos: srdiag/bpm/c11-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-01 x_pos: srdiag/bpm/c12-01/SA_HPosition y_pos: srdiag/bpm/c12-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-02 x_pos: srdiag/bpm/c12-02/SA_HPosition y_pos: srdiag/bpm/c12-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-03 x_pos: srdiag/bpm/c12-03/SA_HPosition y_pos: srdiag/bpm/c12-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-04 x_pos: srdiag/bpm/c12-04/SA_HPosition y_pos: srdiag/bpm/c12-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-05 x_pos: srdiag/bpm/c12-05/SA_HPosition y_pos: srdiag/bpm/c12-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-06 x_pos: srdiag/bpm/c12-06/SA_HPosition y_pos: srdiag/bpm/c12-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-07 x_pos: srdiag/bpm/c12-07/SA_HPosition y_pos: srdiag/bpm/c12-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-08 x_pos: srdiag/bpm/c12-08/SA_HPosition y_pos: srdiag/bpm/c12-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-09 x_pos: srdiag/bpm/c12-09/SA_HPosition y_pos: srdiag/bpm/c12-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-10 x_pos: srdiag/bpm/c12-10/SA_HPosition y_pos: srdiag/bpm/c12-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-01 x_pos: srdiag/bpm/c13-01/SA_HPosition y_pos: srdiag/bpm/c13-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-02 x_pos: srdiag/bpm/c13-02/SA_HPosition y_pos: srdiag/bpm/c13-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-03 x_pos: srdiag/bpm/c13-03/SA_HPosition y_pos: srdiag/bpm/c13-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-04 x_pos: srdiag/bpm/c13-04/SA_HPosition y_pos: srdiag/bpm/c13-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-05 x_pos: srdiag/bpm/c13-05/SA_HPosition y_pos: srdiag/bpm/c13-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-06 x_pos: srdiag/bpm/c13-06/SA_HPosition y_pos: srdiag/bpm/c13-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-07 x_pos: srdiag/bpm/c13-07/SA_HPosition y_pos: srdiag/bpm/c13-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-08 x_pos: srdiag/bpm/c13-08/SA_HPosition y_pos: srdiag/bpm/c13-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-09 x_pos: srdiag/bpm/c13-09/SA_HPosition y_pos: srdiag/bpm/c13-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-10 x_pos: srdiag/bpm/c13-10/SA_HPosition y_pos: srdiag/bpm/c13-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-01 x_pos: srdiag/bpm/c14-01/SA_HPosition y_pos: srdiag/bpm/c14-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-02 x_pos: srdiag/bpm/c14-02/SA_HPosition y_pos: srdiag/bpm/c14-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-03 x_pos: srdiag/bpm/c14-03/SA_HPosition y_pos: srdiag/bpm/c14-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-04 x_pos: srdiag/bpm/c14-04/SA_HPosition y_pos: srdiag/bpm/c14-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-05 x_pos: srdiag/bpm/c14-05/SA_HPosition y_pos: srdiag/bpm/c14-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-06 x_pos: srdiag/bpm/c14-06/SA_HPosition y_pos: srdiag/bpm/c14-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-07 x_pos: srdiag/bpm/c14-07/SA_HPosition y_pos: srdiag/bpm/c14-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-08 x_pos: srdiag/bpm/c14-08/SA_HPosition y_pos: srdiag/bpm/c14-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-09 x_pos: srdiag/bpm/c14-09/SA_HPosition y_pos: srdiag/bpm/c14-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-10 x_pos: srdiag/bpm/c14-10/SA_HPosition y_pos: srdiag/bpm/c14-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-01 x_pos: srdiag/bpm/c15-01/SA_HPosition y_pos: srdiag/bpm/c15-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-02 x_pos: srdiag/bpm/c15-02/SA_HPosition y_pos: srdiag/bpm/c15-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-03 x_pos: srdiag/bpm/c15-03/SA_HPosition y_pos: srdiag/bpm/c15-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-04 x_pos: srdiag/bpm/c15-04/SA_HPosition y_pos: srdiag/bpm/c15-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-05 x_pos: srdiag/bpm/c15-05/SA_HPosition y_pos: srdiag/bpm/c15-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-06 x_pos: srdiag/bpm/c15-06/SA_HPosition y_pos: srdiag/bpm/c15-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-07 x_pos: srdiag/bpm/c15-07/SA_HPosition y_pos: srdiag/bpm/c15-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-08 x_pos: srdiag/bpm/c15-08/SA_HPosition y_pos: srdiag/bpm/c15-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-09 x_pos: srdiag/bpm/c15-09/SA_HPosition y_pos: srdiag/bpm/c15-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-10 x_pos: srdiag/bpm/c15-10/SA_HPosition y_pos: srdiag/bpm/c15-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-01 x_pos: srdiag/bpm/c16-01/SA_HPosition y_pos: srdiag/bpm/c16-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-02 x_pos: srdiag/bpm/c16-02/SA_HPosition y_pos: srdiag/bpm/c16-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-03 x_pos: srdiag/bpm/c16-03/SA_HPosition y_pos: srdiag/bpm/c16-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-04 x_pos: srdiag/bpm/c16-04/SA_HPosition y_pos: srdiag/bpm/c16-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-05 x_pos: srdiag/bpm/c16-05/SA_HPosition y_pos: srdiag/bpm/c16-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-06 x_pos: srdiag/bpm/c16-06/SA_HPosition y_pos: srdiag/bpm/c16-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-07 x_pos: srdiag/bpm/c16-07/SA_HPosition y_pos: srdiag/bpm/c16-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-08 x_pos: srdiag/bpm/c16-08/SA_HPosition y_pos: srdiag/bpm/c16-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-09 x_pos: srdiag/bpm/c16-09/SA_HPosition y_pos: srdiag/bpm/c16-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-10 x_pos: srdiag/bpm/c16-10/SA_HPosition y_pos: srdiag/bpm/c16-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-01 x_pos: srdiag/bpm/c17-01/SA_HPosition y_pos: srdiag/bpm/c17-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-02 x_pos: srdiag/bpm/c17-02/SA_HPosition y_pos: srdiag/bpm/c17-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-03 x_pos: srdiag/bpm/c17-03/SA_HPosition y_pos: srdiag/bpm/c17-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-04 x_pos: srdiag/bpm/c17-04/SA_HPosition y_pos: srdiag/bpm/c17-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-05 x_pos: srdiag/bpm/c17-05/SA_HPosition y_pos: srdiag/bpm/c17-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-06 x_pos: srdiag/bpm/c17-06/SA_HPosition y_pos: srdiag/bpm/c17-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-07 x_pos: srdiag/bpm/c17-07/SA_HPosition y_pos: srdiag/bpm/c17-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-08 x_pos: srdiag/bpm/c17-08/SA_HPosition y_pos: srdiag/bpm/c17-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-09 x_pos: srdiag/bpm/c17-09/SA_HPosition y_pos: srdiag/bpm/c17-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-10 x_pos: srdiag/bpm/c17-10/SA_HPosition y_pos: srdiag/bpm/c17-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-01 x_pos: srdiag/bpm/c18-01/SA_HPosition y_pos: srdiag/bpm/c18-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-02 x_pos: srdiag/bpm/c18-02/SA_HPosition y_pos: srdiag/bpm/c18-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-03 x_pos: srdiag/bpm/c18-03/SA_HPosition y_pos: srdiag/bpm/c18-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-04 x_pos: srdiag/bpm/c18-04/SA_HPosition y_pos: srdiag/bpm/c18-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-05 x_pos: srdiag/bpm/c18-05/SA_HPosition y_pos: srdiag/bpm/c18-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-06 x_pos: srdiag/bpm/c18-06/SA_HPosition y_pos: srdiag/bpm/c18-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-07 x_pos: srdiag/bpm/c18-07/SA_HPosition y_pos: srdiag/bpm/c18-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-08 x_pos: srdiag/bpm/c18-08/SA_HPosition y_pos: srdiag/bpm/c18-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-09 x_pos: srdiag/bpm/c18-09/SA_HPosition y_pos: srdiag/bpm/c18-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-10 x_pos: srdiag/bpm/c18-10/SA_HPosition y_pos: srdiag/bpm/c18-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-01 x_pos: srdiag/bpm/c19-01/SA_HPosition y_pos: srdiag/bpm/c19-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-02 x_pos: srdiag/bpm/c19-02/SA_HPosition y_pos: srdiag/bpm/c19-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-03 x_pos: srdiag/bpm/c19-03/SA_HPosition y_pos: srdiag/bpm/c19-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-04 x_pos: srdiag/bpm/c19-04/SA_HPosition y_pos: srdiag/bpm/c19-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-05 x_pos: srdiag/bpm/c19-05/SA_HPosition y_pos: srdiag/bpm/c19-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-06 x_pos: srdiag/bpm/c19-06/SA_HPosition y_pos: srdiag/bpm/c19-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-07 x_pos: srdiag/bpm/c19-07/SA_HPosition y_pos: srdiag/bpm/c19-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-08 x_pos: srdiag/bpm/c19-08/SA_HPosition y_pos: srdiag/bpm/c19-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-09 x_pos: srdiag/bpm/c19-09/SA_HPosition y_pos: srdiag/bpm/c19-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-10 x_pos: srdiag/bpm/c19-10/SA_HPosition y_pos: srdiag/bpm/c19-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-01 x_pos: srdiag/bpm/c20-01/SA_HPosition y_pos: srdiag/bpm/c20-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-02 x_pos: srdiag/bpm/c20-02/SA_HPosition y_pos: srdiag/bpm/c20-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-03 x_pos: srdiag/bpm/c20-03/SA_HPosition y_pos: srdiag/bpm/c20-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-04 x_pos: srdiag/bpm/c20-04/SA_HPosition y_pos: srdiag/bpm/c20-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-05 x_pos: srdiag/bpm/c20-05/SA_HPosition y_pos: srdiag/bpm/c20-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-06 x_pos: srdiag/bpm/c20-06/SA_HPosition y_pos: srdiag/bpm/c20-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-07 x_pos: srdiag/bpm/c20-07/SA_HPosition y_pos: srdiag/bpm/c20-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-08 x_pos: srdiag/bpm/c20-08/SA_HPosition y_pos: srdiag/bpm/c20-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-09 x_pos: srdiag/bpm/c20-09/SA_HPosition y_pos: srdiag/bpm/c20-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-10 x_pos: srdiag/bpm/c20-10/SA_HPosition y_pos: srdiag/bpm/c20-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-01 x_pos: srdiag/bpm/c21-01/SA_HPosition y_pos: srdiag/bpm/c21-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-02 x_pos: srdiag/bpm/c21-02/SA_HPosition y_pos: srdiag/bpm/c21-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-03 x_pos: srdiag/bpm/c21-03/SA_HPosition y_pos: srdiag/bpm/c21-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-04 x_pos: srdiag/bpm/c21-04/SA_HPosition y_pos: srdiag/bpm/c21-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-05 x_pos: srdiag/bpm/c21-05/SA_HPosition y_pos: srdiag/bpm/c21-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-06 x_pos: srdiag/bpm/c21-06/SA_HPosition y_pos: srdiag/bpm/c21-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-07 x_pos: srdiag/bpm/c21-07/SA_HPosition y_pos: srdiag/bpm/c21-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-08 x_pos: srdiag/bpm/c21-08/SA_HPosition y_pos: srdiag/bpm/c21-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-09 x_pos: srdiag/bpm/c21-09/SA_HPosition y_pos: srdiag/bpm/c21-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-10 x_pos: srdiag/bpm/c21-10/SA_HPosition y_pos: srdiag/bpm/c21-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-01 x_pos: srdiag/bpm/c22-01/SA_HPosition y_pos: srdiag/bpm/c22-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-02 x_pos: srdiag/bpm/c22-02/SA_HPosition y_pos: srdiag/bpm/c22-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-03 x_pos: srdiag/bpm/c22-03/SA_HPosition y_pos: srdiag/bpm/c22-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-04 x_pos: srdiag/bpm/c22-04/SA_HPosition y_pos: srdiag/bpm/c22-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-05 x_pos: srdiag/bpm/c22-05/SA_HPosition y_pos: srdiag/bpm/c22-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-06 x_pos: srdiag/bpm/c22-06/SA_HPosition y_pos: srdiag/bpm/c22-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-07 x_pos: srdiag/bpm/c22-07/SA_HPosition y_pos: srdiag/bpm/c22-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-08 x_pos: srdiag/bpm/c22-08/SA_HPosition y_pos: srdiag/bpm/c22-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-09 x_pos: srdiag/bpm/c22-09/SA_HPosition y_pos: srdiag/bpm/c22-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-10 x_pos: srdiag/bpm/c22-10/SA_HPosition y_pos: srdiag/bpm/c22-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-01 x_pos: srdiag/bpm/c23-01/SA_HPosition y_pos: srdiag/bpm/c23-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-02 x_pos: srdiag/bpm/c23-02/SA_HPosition y_pos: srdiag/bpm/c23-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-03 x_pos: srdiag/bpm/c23-03/SA_HPosition y_pos: srdiag/bpm/c23-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-04 x_pos: srdiag/bpm/c23-04/SA_HPosition y_pos: srdiag/bpm/c23-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-05 x_pos: srdiag/bpm/c23-05/SA_HPosition y_pos: srdiag/bpm/c23-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-06 x_pos: srdiag/bpm/c23-06/SA_HPosition y_pos: srdiag/bpm/c23-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-07 x_pos: srdiag/bpm/c23-07/SA_HPosition y_pos: srdiag/bpm/c23-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-08 x_pos: srdiag/bpm/c23-08/SA_HPosition y_pos: srdiag/bpm/c23-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-09 x_pos: srdiag/bpm/c23-09/SA_HPosition y_pos: srdiag/bpm/c23-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-10 x_pos: srdiag/bpm/c23-10/SA_HPosition y_pos: srdiag/bpm/c23-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-01 x_pos: srdiag/bpm/c24-01/SA_HPosition y_pos: srdiag/bpm/c24-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-02 x_pos: srdiag/bpm/c24-02/SA_HPosition y_pos: srdiag/bpm/c24-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-03 x_pos: srdiag/bpm/c24-03/SA_HPosition y_pos: srdiag/bpm/c24-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-04 x_pos: srdiag/bpm/c24-04/SA_HPosition y_pos: srdiag/bpm/c24-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-05 x_pos: srdiag/bpm/c24-05/SA_HPosition y_pos: srdiag/bpm/c24-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-06 x_pos: srdiag/bpm/c24-06/SA_HPosition y_pos: srdiag/bpm/c24-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-07 x_pos: srdiag/bpm/c24-07/SA_HPosition y_pos: srdiag/bpm/c24-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-08 x_pos: srdiag/bpm/c24-08/SA_HPosition y_pos: srdiag/bpm/c24-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-09 x_pos: srdiag/bpm/c24-09/SA_HPosition y_pos: srdiag/bpm/c24-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-10 x_pos: srdiag/bpm/c24-10/SA_HPosition y_pos: srdiag/bpm/c24-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-01 x_pos: srdiag/bpm/c25-01/SA_HPosition y_pos: srdiag/bpm/c25-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-02 x_pos: srdiag/bpm/c25-02/SA_HPosition y_pos: srdiag/bpm/c25-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-03 x_pos: srdiag/bpm/c25-03/SA_HPosition y_pos: srdiag/bpm/c25-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-04 x_pos: srdiag/bpm/c25-04/SA_HPosition y_pos: srdiag/bpm/c25-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-05 x_pos: srdiag/bpm/c25-05/SA_HPosition y_pos: srdiag/bpm/c25-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-06 x_pos: srdiag/bpm/c25-06/SA_HPosition y_pos: srdiag/bpm/c25-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-07 x_pos: srdiag/bpm/c25-07/SA_HPosition y_pos: srdiag/bpm/c25-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-08 x_pos: srdiag/bpm/c25-08/SA_HPosition y_pos: srdiag/bpm/c25-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-09 x_pos: srdiag/bpm/c25-09/SA_HPosition y_pos: srdiag/bpm/c25-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-10 x_pos: srdiag/bpm/c25-10/SA_HPosition y_pos: srdiag/bpm/c25-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-01 x_pos: srdiag/bpm/c26-01/SA_HPosition y_pos: srdiag/bpm/c26-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-02 x_pos: srdiag/bpm/c26-02/SA_HPosition y_pos: srdiag/bpm/c26-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-03 x_pos: srdiag/bpm/c26-03/SA_HPosition y_pos: srdiag/bpm/c26-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-04 x_pos: srdiag/bpm/c26-04/SA_HPosition y_pos: srdiag/bpm/c26-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-05 x_pos: srdiag/bpm/c26-05/SA_HPosition y_pos: srdiag/bpm/c26-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-06 x_pos: srdiag/bpm/c26-06/SA_HPosition y_pos: srdiag/bpm/c26-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-07 x_pos: srdiag/bpm/c26-07/SA_HPosition y_pos: srdiag/bpm/c26-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-08 x_pos: srdiag/bpm/c26-08/SA_HPosition y_pos: srdiag/bpm/c26-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-09 x_pos: srdiag/bpm/c26-09/SA_HPosition y_pos: srdiag/bpm/c26-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-10 x_pos: srdiag/bpm/c26-10/SA_HPosition y_pos: srdiag/bpm/c26-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-01 x_pos: srdiag/bpm/c27-01/SA_HPosition y_pos: srdiag/bpm/c27-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-02 x_pos: srdiag/bpm/c27-02/SA_HPosition y_pos: srdiag/bpm/c27-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-03 x_pos: srdiag/bpm/c27-03/SA_HPosition y_pos: srdiag/bpm/c27-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-04 x_pos: srdiag/bpm/c27-04/SA_HPosition y_pos: srdiag/bpm/c27-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-05 x_pos: srdiag/bpm/c27-05/SA_HPosition y_pos: srdiag/bpm/c27-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-06 x_pos: srdiag/bpm/c27-06/SA_HPosition y_pos: srdiag/bpm/c27-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-07 x_pos: srdiag/bpm/c27-07/SA_HPosition y_pos: srdiag/bpm/c27-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-08 x_pos: srdiag/bpm/c27-08/SA_HPosition y_pos: srdiag/bpm/c27-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-09 x_pos: srdiag/bpm/c27-09/SA_HPosition y_pos: srdiag/bpm/c27-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-10 x_pos: srdiag/bpm/c27-10/SA_HPosition y_pos: srdiag/bpm/c27-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-01 x_pos: srdiag/bpm/c28-01/SA_HPosition y_pos: srdiag/bpm/c28-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-02 x_pos: srdiag/bpm/c28-02/SA_HPosition y_pos: srdiag/bpm/c28-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-03 x_pos: srdiag/bpm/c28-03/SA_HPosition y_pos: srdiag/bpm/c28-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-04 x_pos: srdiag/bpm/c28-04/SA_HPosition y_pos: srdiag/bpm/c28-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-05 x_pos: srdiag/bpm/c28-05/SA_HPosition y_pos: srdiag/bpm/c28-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-06 x_pos: srdiag/bpm/c28-06/SA_HPosition y_pos: srdiag/bpm/c28-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-07 x_pos: srdiag/bpm/c28-07/SA_HPosition y_pos: srdiag/bpm/c28-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-08 x_pos: srdiag/bpm/c28-08/SA_HPosition y_pos: srdiag/bpm/c28-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-09 x_pos: srdiag/bpm/c28-09/SA_HPosition y_pos: srdiag/bpm/c28-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-10 x_pos: srdiag/bpm/c28-10/SA_HPosition y_pos: srdiag/bpm/c28-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-01 x_pos: srdiag/bpm/c29-01/SA_HPosition y_pos: srdiag/bpm/c29-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-02 x_pos: srdiag/bpm/c29-02/SA_HPosition y_pos: srdiag/bpm/c29-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-03 x_pos: srdiag/bpm/c29-03/SA_HPosition y_pos: srdiag/bpm/c29-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-04 x_pos: srdiag/bpm/c29-04/SA_HPosition y_pos: srdiag/bpm/c29-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-05 x_pos: srdiag/bpm/c29-05/SA_HPosition y_pos: srdiag/bpm/c29-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-06 x_pos: srdiag/bpm/c29-06/SA_HPosition y_pos: srdiag/bpm/c29-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-07 x_pos: srdiag/bpm/c29-07/SA_HPosition y_pos: srdiag/bpm/c29-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-08 x_pos: srdiag/bpm/c29-08/SA_HPosition y_pos: srdiag/bpm/c29-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-09 x_pos: srdiag/bpm/c29-09/SA_HPosition y_pos: srdiag/bpm/c29-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-10 x_pos: srdiag/bpm/c29-10/SA_HPosition y_pos: srdiag/bpm/c29-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-01 x_pos: srdiag/bpm/c30-01/SA_HPosition y_pos: srdiag/bpm/c30-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-02 x_pos: srdiag/bpm/c30-02/SA_HPosition y_pos: srdiag/bpm/c30-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-03 x_pos: srdiag/bpm/c30-03/SA_HPosition y_pos: srdiag/bpm/c30-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-04 x_pos: srdiag/bpm/c30-04/SA_HPosition y_pos: srdiag/bpm/c30-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-05 x_pos: srdiag/bpm/c30-05/SA_HPosition y_pos: srdiag/bpm/c30-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-06 x_pos: srdiag/bpm/c30-06/SA_HPosition y_pos: srdiag/bpm/c30-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-07 x_pos: srdiag/bpm/c30-07/SA_HPosition y_pos: srdiag/bpm/c30-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-08 x_pos: srdiag/bpm/c30-08/SA_HPosition y_pos: srdiag/bpm/c30-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-09 x_pos: srdiag/bpm/c30-09/SA_HPosition y_pos: srdiag/bpm/c30-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-10 x_pos: srdiag/bpm/c30-10/SA_HPosition y_pos: srdiag/bpm/c30-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-01 x_pos: srdiag/bpm/c31-01/SA_HPosition y_pos: srdiag/bpm/c31-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-02 x_pos: srdiag/bpm/c31-02/SA_HPosition y_pos: srdiag/bpm/c31-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-03 x_pos: srdiag/bpm/c31-03/SA_HPosition y_pos: srdiag/bpm/c31-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-04 x_pos: srdiag/bpm/c31-04/SA_HPosition y_pos: srdiag/bpm/c31-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-05 x_pos: srdiag/bpm/c31-05/SA_HPosition y_pos: srdiag/bpm/c31-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-06 x_pos: srdiag/bpm/c31-06/SA_HPosition y_pos: srdiag/bpm/c31-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-07 x_pos: srdiag/bpm/c31-07/SA_HPosition y_pos: srdiag/bpm/c31-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-08 x_pos: srdiag/bpm/c31-08/SA_HPosition y_pos: srdiag/bpm/c31-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-09 x_pos: srdiag/bpm/c31-09/SA_HPosition y_pos: srdiag/bpm/c31-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-10 x_pos: srdiag/bpm/c31-10/SA_HPosition y_pos: srdiag/bpm/c31-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-01 x_pos: srdiag/bpm/c32-01/SA_HPosition y_pos: srdiag/bpm/c32-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-02 x_pos: srdiag/bpm/c32-02/SA_HPosition y_pos: srdiag/bpm/c32-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-03 x_pos: srdiag/bpm/c32-03/SA_HPosition y_pos: srdiag/bpm/c32-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-04 x_pos: srdiag/bpm/c32-04/SA_HPosition y_pos: srdiag/bpm/c32-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-05 x_pos: srdiag/bpm/c32-05/SA_HPosition y_pos: srdiag/bpm/c32-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-06 x_pos: srdiag/bpm/c32-06/SA_HPosition y_pos: srdiag/bpm/c32-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-07 x_pos: srdiag/bpm/c32-07/SA_HPosition y_pos: srdiag/bpm/c32-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-08 x_pos: srdiag/bpm/c32-08/SA_HPosition y_pos: srdiag/bpm/c32-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-09 x_pos: srdiag/bpm/c32-09/SA_HPosition y_pos: srdiag/bpm/c32-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-10 x_pos: srdiag/bpm/c32-10/SA_HPosition y_pos: srdiag/bpm/c32-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-01 x_pos: srdiag/bpm/c01-01/SA_HPosition y_pos: srdiag/bpm/c01-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-02 x_pos: srdiag/bpm/c01-02/SA_HPosition y_pos: srdiag/bpm/c01-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-03 x_pos: srdiag/bpm/c01-03/SA_HPosition y_pos: srdiag/bpm/c01-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-04 x_pos: srdiag/bpm/c01-04/SA_HPosition y_pos: srdiag/bpm/c01-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-05 x_pos: srdiag/bpm/c01-05/SA_HPosition y_pos: srdiag/bpm/c01-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-06 x_pos: srdiag/bpm/c01-06/SA_HPosition y_pos: srdiag/bpm/c01-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-07 x_pos: srdiag/bpm/c01-07/SA_HPosition y_pos: srdiag/bpm/c01-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-08 x_pos: srdiag/bpm/c01-08/SA_HPosition y_pos: srdiag/bpm/c01-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-09 x_pos: srdiag/bpm/c01-09/SA_HPosition y_pos: srdiag/bpm/c01-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-10 x_pos: srdiag/bpm/c01-10/SA_HPosition y_pos: srdiag/bpm/c01-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-01 x_pos: srdiag/bpm/c02-01/SA_HPosition y_pos: srdiag/bpm/c02-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-02 x_pos: srdiag/bpm/c02-02/SA_HPosition y_pos: srdiag/bpm/c02-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-03 x_pos: srdiag/bpm/c02-03/SA_HPosition y_pos: srdiag/bpm/c02-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-04 x_pos: srdiag/bpm/c02-04/SA_HPosition y_pos: srdiag/bpm/c02-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-05 x_pos: srdiag/bpm/c02-05/SA_HPosition y_pos: srdiag/bpm/c02-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-06 x_pos: srdiag/bpm/c02-06/SA_HPosition y_pos: srdiag/bpm/c02-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-07 x_pos: srdiag/bpm/c02-07/SA_HPosition y_pos: srdiag/bpm/c02-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-08 x_pos: srdiag/bpm/c02-08/SA_HPosition y_pos: srdiag/bpm/c02-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-09 x_pos: srdiag/bpm/c02-09/SA_HPosition y_pos: srdiag/bpm/c02-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-10 x_pos: srdiag/bpm/c02-10/SA_HPosition y_pos: srdiag/bpm/c02-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-01 x_pos: srdiag/bpm/c03-01/SA_HPosition y_pos: srdiag/bpm/c03-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-02 x_pos: srdiag/bpm/c03-02/SA_HPosition y_pos: srdiag/bpm/c03-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-03 x_pos: srdiag/bpm/c03-03/SA_HPosition y_pos: srdiag/bpm/c03-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-04 x_pos: srdiag/bpm/c03-04/SA_HPosition y_pos: srdiag/bpm/c03-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-05 x_pos: srdiag/bpm/c03-05/SA_HPosition y_pos: srdiag/bpm/c03-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-06 x_pos: srdiag/bpm/c03-06/SA_HPosition y_pos: srdiag/bpm/c03-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-07 x_pos: srdiag/bpm/c03-07/SA_HPosition y_pos: srdiag/bpm/c03-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-08 x_pos: srdiag/bpm/c03-08/SA_HPosition y_pos: srdiag/bpm/c03-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-09 x_pos: srdiag/bpm/c03-09/SA_HPosition y_pos: srdiag/bpm/c03-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-10 x_pos: srdiag/bpm/c03-10/SA_HPosition y_pos: srdiag/bpm/c03-10/SA_VPosition diff --git a/examples/use_cases/config/soleil_ii/devices.yaml b/examples/use_cases/config/soleil_ii/devices.yaml index e75b0954..c19b50ad 100644 --- a/examples/use_cases/config/soleil_ii/devices.yaml +++ b/examples/use_cases/config/soleil_ii/devices.yaml @@ -1,4 +1,4 @@ -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_001 x_pos: AN01-SD/DG-EPOS/BPM.02/x_pos y_pos: AN01-SD/DG-EPOS/BPM.02/y_pos @@ -54,7 +54,7 @@ physics: - AN01-AR/EM-OCT/OH.02/strength - AN01-AR/EM-COR/OH.02-CQLN.02/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_002 x_pos: AN01-AR/DG-EPOS/BPM.03/x_pos y_pos: AN01-AR/DG-EPOS/BPM.03/y_pos @@ -108,7 +108,7 @@ physics: - AN01-AR/EM-SX/SCF.02/strength - AN01-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_003 x_pos: AN01-AR/DG-EPOS/BPM.04/x_pos y_pos: AN01-AR/DG-EPOS/BPM.04/y_pos @@ -168,7 +168,7 @@ physics: - AN01-AR/EM-SX/SCF.05/strength - AN01-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_004 x_pos: AN01-AR/DG-EPOS/BPM.05/x_pos y_pos: AN01-AR/DG-EPOS/BPM.05/y_pos @@ -228,7 +228,7 @@ physics: - AN01-AR/EM-SX/SCF.08/strength - AN01-AR/EM-COR/SCF.08-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_005 x_pos: AN01-AR/DG-EPOS/BPM.06/x_pos y_pos: AN01-AR/DG-EPOS/BPM.06/y_pos @@ -288,7 +288,7 @@ - AN01-AR/EM-OCT/OC.04/strength - AN01-AR/EM-COR/OC.04-CQLN.13/strength - AN01-AR/EM-COR/OC.04-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_006 x_pos: AN01-AR/DG-EPOS/BPM.07/x_pos y_pos: AN01-AR/DG-EPOS/BPM.07/y_pos @@ -348,7 +348,7 @@ - AN01-AR/EM-OCT/OC.05/strength - AN01-AR/EM-COR/OC.05-CQLN.16/strength - AN01-AR/EM-COR/OC.05-CQLT.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_007 x_pos: AN01-AR/DG-EPOS/BPM.08/x_pos y_pos: AN01-AR/DG-EPOS/BPM.08/y_pos @@ -420,7 +420,7 @@ physics: - AN01-AR/EM-SX/SCD.18/strength - AN01-AR/EM-COR/SCD.18-CDLV.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_008 x_pos: AN01-AR/DG-EPOS/BPM.09/x_pos y_pos: AN01-AR/DG-EPOS/BPM.09/y_pos @@ -462,7 +462,7 @@ physics: - AN01-AR/EM-OCT/OH.03/strength - AN01-AR/EM-COR/OH.03-CQLN.21/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_009 x_pos: AN01-AR/DG-EPOS/BPM.10/x_pos y_pos: AN01-AR/DG-EPOS/BPM.10/y_pos @@ -494,11 +494,11 @@ - AN01-AR/EM-OCT/OH.04/strength - AN01-AR/EM-COR/OH.04-CQLN.22/strength - AN01-AR/EM-COR/OH.04-CQLT.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_010 x_pos: AN02-SD/DG-EPOS/BPM.01/x_pos y_pos: AN02-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_011 x_pos: AN02-SD/DG-EPOS/BPM.02/x_pos y_pos: AN02-SD/DG-EPOS/BPM.02/y_pos @@ -530,7 +530,7 @@ - AN02-AR/EM-SX/SHF.01/strength - AN02-AR/EM-COR/SHF.01-CDLH.01/strength - AN02-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_012 x_pos: AN02-AR/DG-EPOS/BPM.03/x_pos y_pos: AN02-AR/DG-EPOS/BPM.03/y_pos @@ -604,7 +604,7 @@ physics: - AN02-AR/EM-SX/SCF.02/strength - AN02-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_013 x_pos: AN02-AR/DG-EPOS/BPM.04/x_pos y_pos: AN02-AR/DG-EPOS/BPM.04/y_pos @@ -664,7 +664,7 @@ physics: - AN02-AR/EM-SX/SCF.05/strength - AN02-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_014 x_pos: AN02-AR/DG-EPOS/BPM.05/x_pos y_pos: AN02-AR/DG-EPOS/BPM.05/y_pos @@ -736,7 +736,7 @@ physics: - AN02-AR/EM-SX/SCD.09/strength - AN02-AR/EM-COR/SCD.09-CDLV.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_015 x_pos: AN02-AR/DG-EPOS/BPM.06/x_pos y_pos: AN02-AR/DG-EPOS/BPM.06/y_pos @@ -772,7 +772,7 @@ type: pyaml.magnet.identity_model unit: 1/m physics: AN02-AR/EM-COR/CQLN.12/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_016 x_pos: AN02-AR/DG-EPOS/BPM.07/x_pos y_pos: AN02-AR/DG-EPOS/BPM.07/y_pos @@ -804,11 +804,11 @@ - AN02-AR/EM-OCT/OH.03/strength - AN02-AR/EM-COR/OH.03-CQLN.13/strength - AN02-AR/EM-COR/OH.03-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_017 x_pos: AN03-SD/DG-EPOS/BPM.01/x_pos y_pos: AN03-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_018 x_pos: AN03-SD/DG-EPOS/BPM.02/x_pos y_pos: AN03-SD/DG-EPOS/BPM.02/y_pos @@ -840,7 +840,7 @@ - AN03-AR/EM-SX/SHF.01/strength - AN03-AR/EM-COR/SHF.01-CDLH.01/strength - AN03-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_019 x_pos: AN03-AR/DG-EPOS/BPM.03/x_pos y_pos: AN03-AR/DG-EPOS/BPM.03/y_pos @@ -908,7 +908,7 @@ physics: - AN03-AR/EM-SX/SCF.02/strength - AN03-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_020 x_pos: AN03-AR/DG-EPOS/BPM.04/x_pos y_pos: AN03-AR/DG-EPOS/BPM.04/y_pos @@ -968,7 +968,7 @@ physics: - AN03-AR/EM-SX/SCF.05/strength - AN03-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_021 x_pos: AN03-AR/DG-EPOS/BPM.05/x_pos y_pos: AN03-AR/DG-EPOS/BPM.05/y_pos @@ -1028,7 +1028,7 @@ physics: - AN03-AR/EM-SX/SCF.08/strength - AN03-AR/EM-COR/SCF.08-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_022 x_pos: AN03-AR/DG-EPOS/BPM.06/x_pos y_pos: AN03-AR/DG-EPOS/BPM.06/y_pos @@ -1088,7 +1088,7 @@ - AN03-AR/EM-OCT/OC.04/strength - AN03-AR/EM-COR/OC.04-CQLN.13/strength - AN03-AR/EM-COR/OC.04-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_023 x_pos: AN03-AR/DG-EPOS/BPM.07/x_pos y_pos: AN03-AR/DG-EPOS/BPM.07/y_pos @@ -1148,7 +1148,7 @@ - AN03-AR/EM-OCT/OC.05/strength - AN03-AR/EM-COR/OC.05-CQLN.16/strength - AN03-AR/EM-COR/OC.05-CQLT.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_024 x_pos: AN03-AR/DG-EPOS/BPM.08/x_pos y_pos: AN03-AR/DG-EPOS/BPM.08/y_pos @@ -1220,7 +1220,7 @@ physics: - AN03-AR/EM-SX/SCD.18/strength - AN03-AR/EM-COR/SCD.18-CDLV.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_025 x_pos: AN03-AR/DG-EPOS/BPM.09/x_pos y_pos: AN03-AR/DG-EPOS/BPM.09/y_pos @@ -1256,7 +1256,7 @@ type: pyaml.magnet.identity_model unit: 1/m physics: AN03-AR/EM-COR/CQLN.21/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_026 x_pos: AN03-AR/DG-EPOS/BPM.10/x_pos y_pos: AN03-AR/DG-EPOS/BPM.10/y_pos @@ -1288,11 +1288,11 @@ - AN03-AR/EM-OCT/OH.02/strength - AN03-AR/EM-COR/OH.02-CQLN.22/strength - AN03-AR/EM-COR/OH.02-CQLT.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_027 x_pos: AN04-SD/DG-EPOS/BPM.01/x_pos y_pos: AN04-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_028 x_pos: AN04-SD/DG-EPOS/BPM.02/x_pos y_pos: AN04-SD/DG-EPOS/BPM.02/y_pos @@ -1324,7 +1324,7 @@ - AN04-AR/EM-SX/SHF.01/strength - AN04-AR/EM-COR/SHF.01-CDLH.01/strength - AN04-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_029 x_pos: AN04-AR/DG-EPOS/BPM.03/x_pos y_pos: AN04-AR/DG-EPOS/BPM.03/y_pos @@ -1392,7 +1392,7 @@ physics: - AN04-AR/EM-SX/SCF.02/strength - AN04-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_030 x_pos: AN04-AR/DG-EPOS/BPM.04/x_pos y_pos: AN04-AR/DG-EPOS/BPM.04/y_pos @@ -1452,7 +1452,7 @@ - AN04-AR/EM-OCT/OC.02/strength - AN04-AR/EM-COR/OC.02-CQLN.07/strength - AN04-AR/EM-COR/OC.02-CQLT.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_031 x_pos: AN04-AR/DG-EPOS/BPM.05/x_pos y_pos: AN04-AR/DG-EPOS/BPM.05/y_pos @@ -1524,7 +1524,7 @@ physics: - AN04-AR/EM-SX/SCD.09/strength - AN04-AR/EM-COR/SCD.09-CDLV.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_032 x_pos: AN04-AR/DG-EPOS/BPM.06/x_pos y_pos: AN04-AR/DG-EPOS/BPM.06/y_pos @@ -1566,7 +1566,7 @@ physics: - AN04-AR/EM-OCT/OH.02/strength - AN04-AR/EM-COR/OH.02-CQLN.12/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_033 x_pos: AN04-AR/DG-EPOS/BPM.07/x_pos y_pos: AN04-AR/DG-EPOS/BPM.07/y_pos @@ -1598,11 +1598,11 @@ - AN04-AR/EM-OCT/OH.03/strength - AN04-AR/EM-COR/OH.03-CQLN.13/strength - AN04-AR/EM-COR/OH.03-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_034 x_pos: AN05-SD/DG-EPOS/BPM.01/x_pos y_pos: AN05-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_035 x_pos: AN05-SD/DG-EPOS/BPM.02/x_pos y_pos: AN05-SD/DG-EPOS/BPM.02/y_pos @@ -1634,7 +1634,7 @@ - AN05-AR/EM-SX/SHF.01/strength - AN05-AR/EM-COR/SHF.01-CDLH.01/strength - AN05-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_036 x_pos: AN05-AR/DG-EPOS/BPM.03/x_pos y_pos: AN05-AR/DG-EPOS/BPM.03/y_pos @@ -1708,7 +1708,7 @@ physics: - AN05-AR/EM-SX/SCF.02/strength - AN05-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_037 x_pos: AN05-AR/DG-EPOS/BPM.04/x_pos y_pos: AN05-AR/DG-EPOS/BPM.04/y_pos @@ -1768,7 +1768,7 @@ physics: - AN05-AR/EM-SX/SCF.05/strength - AN05-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_038 x_pos: AN05-AR/DG-EPOS/BPM.05/x_pos y_pos: AN05-AR/DG-EPOS/BPM.05/y_pos @@ -1828,7 +1828,7 @@ physics: - AN05-AR/EM-SX/SCF.08/strength - AN05-AR/EM-COR/SCF.08-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_039 x_pos: AN05-AR/DG-EPOS/BPM.06/x_pos y_pos: AN05-AR/DG-EPOS/BPM.06/y_pos @@ -1888,7 +1888,7 @@ - AN05-AR/EM-OCT/OC.04/strength - AN05-AR/EM-COR/OC.04-CQLN.13/strength - AN05-AR/EM-COR/OC.04-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_040 x_pos: AN05-AR/DG-EPOS/BPM.07/x_pos y_pos: AN05-AR/DG-EPOS/BPM.07/y_pos @@ -1948,7 +1948,7 @@ - AN05-AR/EM-OCT/OC.05/strength - AN05-AR/EM-COR/OC.05-CQLN.16/strength - AN05-AR/EM-COR/OC.05-CQLT.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_041 x_pos: AN05-AR/DG-EPOS/BPM.08/x_pos y_pos: AN05-AR/DG-EPOS/BPM.08/y_pos @@ -2020,7 +2020,7 @@ physics: - AN05-AR/EM-SX/SCD.18/strength - AN05-AR/EM-COR/SCD.18-CDLV.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_042 x_pos: AN05-AR/DG-EPOS/BPM.09/x_pos y_pos: AN05-AR/DG-EPOS/BPM.09/y_pos @@ -2042,7 +2042,7 @@ physics: - AN05-AR/EM-SX/SHD.04/strength - AN05-AR/EM-COR/SHD.04-CDLV.09/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_043 x_pos: AN05-AR/DG-EPOS/BPM.10/x_pos y_pos: AN05-AR/DG-EPOS/BPM.10/y_pos @@ -2098,11 +2098,11 @@ - AN05-AR/EM-OCT/OH.04/strength - AN05-AR/EM-COR/OH.04-CQLN.22/strength - AN05-AR/EM-COR/OH.04-CQLT.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_044 x_pos: AN06-SD/DG-EPOS/BPM.01/x_pos y_pos: AN06-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_045 x_pos: AN06-SD/DG-EPOS/BPM.02/x_pos y_pos: AN06-SD/DG-EPOS/BPM.02/y_pos @@ -2158,11 +2158,11 @@ physics: - AN06-SD/EM-OCT/OH.02/strength - AN06-SD/EM-COR/OH.02-CQLN.02/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_046 x_pos: AN06-SD/DG-EPOS/BPM.03/x_pos y_pos: AN06-SD/DG-EPOS/BPM.03/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_047 x_pos: AN06-SD/DG-EPOS/BPM.04/x_pos y_pos: AN06-SD/DG-EPOS/BPM.04/y_pos @@ -2218,7 +2218,7 @@ physics: - AN06-AR/EM-SX/SHD.02/strength - AN06-AR/EM-COR/SHD.02-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_048 x_pos: AN06-AR/DG-EPOS/BPM.05/x_pos y_pos: AN06-AR/DG-EPOS/BPM.05/y_pos @@ -2272,7 +2272,7 @@ physics: - AN06-AR/EM-SX/SCF.02/strength - AN06-AR/EM-COR/SCF.02-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_049 x_pos: AN06-AR/DG-EPOS/BPM.06/x_pos y_pos: AN06-AR/DG-EPOS/BPM.06/y_pos @@ -2332,7 +2332,7 @@ physics: - AN06-AR/EM-SX/SCF.05/strength - AN06-AR/EM-COR/SCF.05-CDLH.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_050 x_pos: AN06-AR/DG-EPOS/BPM.07/x_pos y_pos: AN06-AR/DG-EPOS/BPM.07/y_pos @@ -2392,7 +2392,7 @@ physics: - AN06-AR/EM-SX/SCF.08/strength - AN06-AR/EM-COR/SCF.08-CDLH.07/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_051 x_pos: AN06-AR/DG-EPOS/BPM.08/x_pos y_pos: AN06-AR/DG-EPOS/BPM.08/y_pos @@ -2452,7 +2452,7 @@ - AN06-AR/EM-OCT/OC.04/strength - AN06-AR/EM-COR/OC.04-CQLN.13/strength - AN06-AR/EM-COR/OC.04-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_052 x_pos: AN06-AR/DG-EPOS/BPM.09/x_pos y_pos: AN06-AR/DG-EPOS/BPM.09/y_pos @@ -2512,7 +2512,7 @@ - AN06-AR/EM-OCT/OC.05/strength - AN06-AR/EM-COR/OC.05-CQLN.16/strength - AN06-AR/EM-COR/OC.05-CQLT.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_053 x_pos: AN06-AR/DG-EPOS/BPM.10/x_pos y_pos: AN06-AR/DG-EPOS/BPM.10/y_pos @@ -2584,7 +2584,7 @@ physics: - AN06-AR/EM-SX/SCD.18/strength - AN06-AR/EM-COR/SCD.18-CDLV.10/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_054 x_pos: AN06-AR/DG-EPOS/BPM.11/x_pos y_pos: AN06-AR/DG-EPOS/BPM.11/y_pos @@ -2626,7 +2626,7 @@ physics: - AN06-AR/EM-OCT/OH.03/strength - AN06-AR/EM-COR/OH.03-CQLN.21/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_055 x_pos: AN06-AR/DG-EPOS/BPM.12/x_pos y_pos: AN06-AR/DG-EPOS/BPM.12/y_pos @@ -2658,11 +2658,11 @@ - AN06-AR/EM-OCT/OH.04/strength - AN06-AR/EM-COR/OH.04-CQLN.22/strength - AN06-AR/EM-COR/OH.04-CQLT.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_056 x_pos: AN07-SD/DG-EPOS/BPM.01/x_pos y_pos: AN07-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_057 x_pos: AN07-SD/DG-EPOS/BPM.02/x_pos y_pos: AN07-SD/DG-EPOS/BPM.02/y_pos @@ -2694,7 +2694,7 @@ - AN07-AR/EM-SX/SHF.01/strength - AN07-AR/EM-COR/SHF.01-CDLH.01/strength - AN07-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_058 x_pos: AN07-AR/DG-EPOS/BPM.03/x_pos y_pos: AN07-AR/DG-EPOS/BPM.03/y_pos @@ -2768,7 +2768,7 @@ physics: - AN07-AR/EM-SX/SCF.02/strength - AN07-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_059 x_pos: AN07-AR/DG-EPOS/BPM.04/x_pos y_pos: AN07-AR/DG-EPOS/BPM.04/y_pos @@ -2828,7 +2828,7 @@ physics: - AN07-AR/EM-SX/SCF.05/strength - AN07-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_060 x_pos: AN07-AR/DG-EPOS/BPM.05/x_pos y_pos: AN07-AR/DG-EPOS/BPM.05/y_pos @@ -2900,7 +2900,7 @@ physics: - AN07-AR/EM-SX/SCD.09/strength - AN07-AR/EM-COR/SCD.09-CDLV.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_061 x_pos: AN07-AR/DG-EPOS/BPM.06/x_pos y_pos: AN07-AR/DG-EPOS/BPM.06/y_pos @@ -2936,7 +2936,7 @@ type: pyaml.magnet.identity_model unit: 1/m physics: AN07-AR/EM-COR/CQLN.12/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_062 x_pos: AN07-AR/DG-EPOS/BPM.07/x_pos y_pos: AN07-AR/DG-EPOS/BPM.07/y_pos @@ -2968,11 +2968,11 @@ - AN07-AR/EM-OCT/OH.03/strength - AN07-AR/EM-COR/OH.03-CQLN.13/strength - AN07-AR/EM-COR/OH.03-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_063 x_pos: AN08-SD/DG-EPOS/BPM.01/x_pos y_pos: AN08-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_064 x_pos: AN08-SD/DG-EPOS/BPM.02/x_pos y_pos: AN08-SD/DG-EPOS/BPM.02/y_pos @@ -3004,7 +3004,7 @@ - AN08-AR/EM-SX/SHF.01/strength - AN08-AR/EM-COR/SHF.01-CDLH.01/strength - AN08-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_065 x_pos: AN08-AR/DG-EPOS/BPM.03/x_pos y_pos: AN08-AR/DG-EPOS/BPM.03/y_pos @@ -3072,7 +3072,7 @@ physics: - AN08-AR/EM-SX/SCF.02/strength - AN08-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_066 x_pos: AN08-AR/DG-EPOS/BPM.04/x_pos y_pos: AN08-AR/DG-EPOS/BPM.04/y_pos @@ -3132,7 +3132,7 @@ physics: - AN08-AR/EM-SX/SCF.05/strength - AN08-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_067 x_pos: AN08-AR/DG-EPOS/BPM.05/x_pos y_pos: AN08-AR/DG-EPOS/BPM.05/y_pos @@ -3192,7 +3192,7 @@ physics: - AN08-AR/EM-SX/SCF.08/strength - AN08-AR/EM-COR/SCF.08-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_068 x_pos: AN08-AR/DG-EPOS/BPM.06/x_pos y_pos: AN08-AR/DG-EPOS/BPM.06/y_pos @@ -3252,7 +3252,7 @@ - AN08-AR/EM-OCT/OC.04/strength - AN08-AR/EM-COR/OC.04-CQLN.13/strength - AN08-AR/EM-COR/OC.04-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_069 x_pos: AN08-AR/DG-EPOS/BPM.07/x_pos y_pos: AN08-AR/DG-EPOS/BPM.07/y_pos @@ -3312,7 +3312,7 @@ - AN08-AR/EM-OCT/OC.05/strength - AN08-AR/EM-COR/OC.05-CQLN.16/strength - AN08-AR/EM-COR/OC.05-CQLT.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_070 x_pos: AN08-AR/DG-EPOS/BPM.08/x_pos y_pos: AN08-AR/DG-EPOS/BPM.08/y_pos @@ -3384,7 +3384,7 @@ physics: - AN08-AR/EM-SX/SCD.18/strength - AN08-AR/EM-COR/SCD.18-CDLV.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_071 x_pos: AN08-AR/DG-EPOS/BPM.09/x_pos y_pos: AN08-AR/DG-EPOS/BPM.09/y_pos @@ -3420,7 +3420,7 @@ type: pyaml.magnet.identity_model unit: 1/m physics: AN08-AR/EM-COR/CQLN.21/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_072 x_pos: AN08-AR/DG-EPOS/BPM.10/x_pos y_pos: AN08-AR/DG-EPOS/BPM.10/y_pos @@ -3452,11 +3452,11 @@ - AN08-AR/EM-OCT/OH.02/strength - AN08-AR/EM-COR/OH.02-CQLN.22/strength - AN08-AR/EM-COR/OH.02-CQLT.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_073 x_pos: AN09-SD/DG-EPOS/BPM.01/x_pos y_pos: AN09-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_074 x_pos: AN09-SD/DG-EPOS/BPM.02/x_pos y_pos: AN09-SD/DG-EPOS/BPM.02/y_pos @@ -3488,7 +3488,7 @@ - AN09-AR/EM-SX/SHF.01/strength - AN09-AR/EM-COR/SHF.01-CDLH.01/strength - AN09-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_075 x_pos: AN09-AR/DG-EPOS/BPM.03/x_pos y_pos: AN09-AR/DG-EPOS/BPM.03/y_pos @@ -3556,7 +3556,7 @@ physics: - AN09-AR/EM-SX/SCF.02/strength - AN09-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_076 x_pos: AN09-AR/DG-EPOS/BPM.04/x_pos y_pos: AN09-AR/DG-EPOS/BPM.04/y_pos @@ -3616,7 +3616,7 @@ - AN09-AR/EM-OCT/OC.02/strength - AN09-AR/EM-COR/OC.02-CQLN.07/strength - AN09-AR/EM-COR/OC.02-CQLT.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_077 x_pos: AN09-AR/DG-EPOS/BPM.05/x_pos y_pos: AN09-AR/DG-EPOS/BPM.05/y_pos @@ -3688,7 +3688,7 @@ physics: - AN09-AR/EM-SX/SCD.09/strength - AN09-AR/EM-COR/SCD.09-CDLV.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_078 x_pos: AN09-AR/DG-EPOS/BPM.06/x_pos y_pos: AN09-AR/DG-EPOS/BPM.06/y_pos @@ -3730,7 +3730,7 @@ physics: - AN09-AR/EM-OCT/OH.02/strength - AN09-AR/EM-COR/OH.02-CQLN.12/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_079 x_pos: AN09-AR/DG-EPOS/BPM.07/x_pos y_pos: AN09-AR/DG-EPOS/BPM.07/y_pos @@ -3762,11 +3762,11 @@ - AN09-AR/EM-OCT/OH.03/strength - AN09-AR/EM-COR/OH.03-CQLN.13/strength - AN09-AR/EM-COR/OH.03-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_080 x_pos: AN10-SD/DG-EPOS/BPM.01/x_pos y_pos: AN10-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_081 x_pos: AN10-SD/DG-EPOS/BPM.02/x_pos y_pos: AN10-SD/DG-EPOS/BPM.02/y_pos @@ -3798,7 +3798,7 @@ - AN10-AR/EM-SX/SHF.01/strength - AN10-AR/EM-COR/SHF.01-CDLH.01/strength - AN10-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_082 x_pos: AN10-AR/DG-EPOS/BPM.03/x_pos y_pos: AN10-AR/DG-EPOS/BPM.03/y_pos @@ -3872,7 +3872,7 @@ physics: - AN10-AR/EM-SX/SCF.02/strength - AN10-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_083 x_pos: AN10-AR/DG-EPOS/BPM.04/x_pos y_pos: AN10-AR/DG-EPOS/BPM.04/y_pos @@ -3932,7 +3932,7 @@ physics: - AN10-AR/EM-SX/SCF.05/strength - AN10-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_084 x_pos: AN10-AR/DG-EPOS/BPM.05/x_pos y_pos: AN10-AR/DG-EPOS/BPM.05/y_pos @@ -3992,7 +3992,7 @@ physics: - AN10-AR/EM-SX/SCF.08/strength - AN10-AR/EM-COR/SCF.08-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_085 x_pos: AN10-AR/DG-EPOS/BPM.06/x_pos y_pos: AN10-AR/DG-EPOS/BPM.06/y_pos @@ -4052,7 +4052,7 @@ - AN10-AR/EM-OCT/OC.04/strength - AN10-AR/EM-COR/OC.04-CQLN.13/strength - AN10-AR/EM-COR/OC.04-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_086 x_pos: AN10-AR/DG-EPOS/BPM.07/x_pos y_pos: AN10-AR/DG-EPOS/BPM.07/y_pos @@ -4112,7 +4112,7 @@ - AN10-AR/EM-OCT/OC.05/strength - AN10-AR/EM-COR/OC.05-CQLN.16/strength - AN10-AR/EM-COR/OC.05-CQLT.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_087 x_pos: AN10-AR/DG-EPOS/BPM.08/x_pos y_pos: AN10-AR/DG-EPOS/BPM.08/y_pos @@ -4184,7 +4184,7 @@ physics: - AN10-AR/EM-SX/SCD.18/strength - AN10-AR/EM-COR/SCD.18-CDLV.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_088 x_pos: AN10-AR/DG-EPOS/BPM.09/x_pos y_pos: AN10-AR/DG-EPOS/BPM.09/y_pos @@ -4206,7 +4206,7 @@ physics: - AN10-AR/EM-SX/SHD.04/strength - AN10-AR/EM-COR/SHD.04-CDLV.09/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_089 x_pos: AN10-AR/DG-EPOS/BPM.10/x_pos y_pos: AN10-AR/DG-EPOS/BPM.10/y_pos @@ -4262,11 +4262,11 @@ - AN10-AR/EM-SX/SHF.06/strength - AN10-AR/EM-COR/SHF.06-CDLH.10/strength - AN10-AR/EM-COR/SHF.06-CDLV.10/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_090 x_pos: AN11-SD/DG-EPOS/BPM.01/x_pos y_pos: AN11-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_091 x_pos: AN11-SD/DG-EPOS/BPM.02/x_pos y_pos: AN11-SD/DG-EPOS/BPM.02/y_pos @@ -4322,7 +4322,7 @@ physics: - AN11-AR/EM-OCT/OH.02/strength - AN11-AR/EM-COR/OH.02-CQLN.02/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_092 x_pos: AN11-AR/DG-EPOS/BPM.03/x_pos y_pos: AN11-AR/DG-EPOS/BPM.03/y_pos @@ -4376,7 +4376,7 @@ physics: - AN11-AR/EM-SX/SCF.02/strength - AN11-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_093 x_pos: AN11-AR/DG-EPOS/BPM.04/x_pos y_pos: AN11-AR/DG-EPOS/BPM.04/y_pos @@ -4436,7 +4436,7 @@ physics: - AN11-AR/EM-SX/SCF.05/strength - AN11-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_094 x_pos: AN11-AR/DG-EPOS/BPM.05/x_pos y_pos: AN11-AR/DG-EPOS/BPM.05/y_pos @@ -4496,7 +4496,7 @@ physics: - AN11-AR/EM-SX/SCF.08/strength - AN11-AR/EM-COR/SCF.08-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_095 x_pos: AN11-AR/DG-EPOS/BPM.06/x_pos y_pos: AN11-AR/DG-EPOS/BPM.06/y_pos @@ -4556,7 +4556,7 @@ - AN11-AR/EM-OCT/OC.04/strength - AN11-AR/EM-COR/OC.04-CQLN.13/strength - AN11-AR/EM-COR/OC.04-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_096 x_pos: AN11-AR/DG-EPOS/BPM.07/x_pos y_pos: AN11-AR/DG-EPOS/BPM.07/y_pos @@ -4616,7 +4616,7 @@ - AN11-AR/EM-OCT/OC.05/strength - AN11-AR/EM-COR/OC.05-CQLN.16/strength - AN11-AR/EM-COR/OC.05-CQLT.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_097 x_pos: AN11-AR/DG-EPOS/BPM.08/x_pos y_pos: AN11-AR/DG-EPOS/BPM.08/y_pos @@ -4688,7 +4688,7 @@ physics: - AN11-AR/EM-SX/SCD.18/strength - AN11-AR/EM-COR/SCD.18-CDLV.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_098 x_pos: AN11-AR/DG-EPOS/BPM.09/x_pos y_pos: AN11-AR/DG-EPOS/BPM.09/y_pos @@ -4730,7 +4730,7 @@ physics: - AN11-AR/EM-OCT/OH.03/strength - AN11-AR/EM-COR/OH.03-CQLN.21/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_099 x_pos: AN11-AR/DG-EPOS/BPM.10/x_pos y_pos: AN11-AR/DG-EPOS/BPM.10/y_pos @@ -4762,11 +4762,11 @@ - AN11-AR/EM-OCT/OH.04/strength - AN11-AR/EM-COR/OH.04-CQLN.22/strength - AN11-AR/EM-COR/OH.04-CQLT.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_100 x_pos: AN12-SD/DG-EPOS/BPM.01/x_pos y_pos: AN12-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_101 x_pos: AN12-SD/DG-EPOS/BPM.02/x_pos y_pos: AN12-SD/DG-EPOS/BPM.02/y_pos @@ -4798,7 +4798,7 @@ - AN12-AR/EM-SX/SHF.01/strength - AN12-AR/EM-COR/SHF.01-CDLH.01/strength - AN12-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_102 x_pos: AN12-AR/DG-EPOS/BPM.03/x_pos y_pos: AN12-AR/DG-EPOS/BPM.03/y_pos @@ -4872,7 +4872,7 @@ physics: - AN12-AR/EM-SX/SCF.02/strength - AN12-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_103 x_pos: AN12-AR/DG-EPOS/BPM.04/x_pos y_pos: AN12-AR/DG-EPOS/BPM.04/y_pos @@ -4932,7 +4932,7 @@ physics: - AN12-AR/EM-SX/SCF.05/strength - AN12-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_104 x_pos: AN12-AR/DG-EPOS/BPM.05/x_pos y_pos: AN12-AR/DG-EPOS/BPM.05/y_pos @@ -5004,7 +5004,7 @@ physics: - AN12-AR/EM-SX/SCD.09/strength - AN12-AR/EM-COR/SCD.09-CDLV.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_105 x_pos: AN12-AR/DG-EPOS/BPM.06/x_pos y_pos: AN12-AR/DG-EPOS/BPM.06/y_pos @@ -5040,7 +5040,7 @@ type: pyaml.magnet.identity_model unit: 1/m physics: AN12-AR/EM-COR/CQLN.12/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_106 x_pos: AN12-AR/DG-EPOS/BPM.07/x_pos y_pos: AN12-AR/DG-EPOS/BPM.07/y_pos @@ -5072,11 +5072,11 @@ - AN12-AR/EM-OCT/OH.03/strength - AN12-AR/EM-COR/OH.03-CQLN.13/strength - AN12-AR/EM-COR/OH.03-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_107 x_pos: AN13-SD/DG-EPOS/BPM.01/x_pos y_pos: AN13-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_108 x_pos: AN13-SD/DG-EPOS/BPM.02/x_pos y_pos: AN13-SD/DG-EPOS/BPM.02/y_pos @@ -5108,7 +5108,7 @@ - AN13-AR/EM-SX/SHF.01/strength - AN13-AR/EM-COR/SHF.01-CDLH.01/strength - AN13-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_109 x_pos: AN13-AR/DG-EPOS/BPM.03/x_pos y_pos: AN13-AR/DG-EPOS/BPM.03/y_pos @@ -5176,7 +5176,7 @@ physics: - AN13-AR/EM-SX/SCF.02/strength - AN13-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_110 x_pos: AN13-AR/DG-EPOS/BPM.04/x_pos y_pos: AN13-AR/DG-EPOS/BPM.04/y_pos @@ -5236,7 +5236,7 @@ physics: - AN13-AR/EM-SX/SCF.05/strength - AN13-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_111 x_pos: AN13-AR/DG-EPOS/BPM.05/x_pos y_pos: AN13-AR/DG-EPOS/BPM.05/y_pos @@ -5296,7 +5296,7 @@ physics: - AN13-AR/EM-SX/SCF.08/strength - AN13-AR/EM-COR/SCF.08-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_112 x_pos: AN13-AR/DG-EPOS/BPM.06/x_pos y_pos: AN13-AR/DG-EPOS/BPM.06/y_pos @@ -5356,7 +5356,7 @@ - AN13-AR/EM-OCT/OC.04/strength - AN13-AR/EM-COR/OC.04-CQLN.13/strength - AN13-AR/EM-COR/OC.04-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_113 x_pos: AN13-AR/DG-EPOS/BPM.07/x_pos y_pos: AN13-AR/DG-EPOS/BPM.07/y_pos @@ -5416,7 +5416,7 @@ - AN13-AR/EM-OCT/OC.05/strength - AN13-AR/EM-COR/OC.05-CQLN.16/strength - AN13-AR/EM-COR/OC.05-CQLT.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_114 x_pos: AN13-AR/DG-EPOS/BPM.08/x_pos y_pos: AN13-AR/DG-EPOS/BPM.08/y_pos @@ -5488,7 +5488,7 @@ physics: - AN13-AR/EM-SX/SCD.18/strength - AN13-AR/EM-COR/SCD.18-CDLV.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_115 x_pos: AN13-AR/DG-EPOS/BPM.09/x_pos y_pos: AN13-AR/DG-EPOS/BPM.09/y_pos @@ -5524,7 +5524,7 @@ type: pyaml.magnet.identity_model unit: 1/m physics: AN13-AR/EM-COR/CQLN.21/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_116 x_pos: AN13-AR/DG-EPOS/BPM.10/x_pos y_pos: AN13-AR/DG-EPOS/BPM.10/y_pos @@ -5556,11 +5556,11 @@ - AN13-AR/EM-OCT/OH.02/strength - AN13-AR/EM-COR/OH.02-CQLN.22/strength - AN13-AR/EM-COR/OH.02-CQLT.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_117 x_pos: AN14-SD/DG-EPOS/BPM.01/x_pos y_pos: AN14-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_118 x_pos: AN14-SD/DG-EPOS/BPM.02/x_pos y_pos: AN14-SD/DG-EPOS/BPM.02/y_pos @@ -5592,7 +5592,7 @@ - AN14-AR/EM-SX/SHF.01/strength - AN14-AR/EM-COR/SHF.01-CDLH.01/strength - AN14-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_119 x_pos: AN14-AR/DG-EPOS/BPM.03/x_pos y_pos: AN14-AR/DG-EPOS/BPM.03/y_pos @@ -5660,7 +5660,7 @@ physics: - AN14-AR/EM-SX/SCF.02/strength - AN14-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_120 x_pos: AN14-AR/DG-EPOS/BPM.04/x_pos y_pos: AN14-AR/DG-EPOS/BPM.04/y_pos @@ -5720,7 +5720,7 @@ - AN14-AR/EM-OCT/OC.02/strength - AN14-AR/EM-COR/OC.02-CQLN.07/strength - AN14-AR/EM-COR/OC.02-CQLT.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_121 x_pos: AN14-AR/DG-EPOS/BPM.05/x_pos y_pos: AN14-AR/DG-EPOS/BPM.05/y_pos @@ -5792,7 +5792,7 @@ physics: - AN14-AR/EM-SX/SCD.09/strength - AN14-AR/EM-COR/SCD.09-CDLV.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_122 x_pos: AN14-AR/DG-EPOS/BPM.06/x_pos y_pos: AN14-AR/DG-EPOS/BPM.06/y_pos @@ -5834,7 +5834,7 @@ physics: - AN14-AR/EM-OCT/OH.02/strength - AN14-AR/EM-COR/OH.02-CQLN.12/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_123 x_pos: AN14-AR/DG-EPOS/BPM.07/x_pos y_pos: AN14-AR/DG-EPOS/BPM.07/y_pos @@ -5866,11 +5866,11 @@ - AN14-AR/EM-OCT/OH.03/strength - AN14-AR/EM-COR/OH.03-CQLN.13/strength - AN14-AR/EM-COR/OH.03-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_124 x_pos: AN15-SD/DG-EPOS/BPM.01/x_pos y_pos: AN15-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_125 x_pos: AN15-SD/DG-EPOS/BPM.02/x_pos y_pos: AN15-SD/DG-EPOS/BPM.02/y_pos @@ -5902,7 +5902,7 @@ - AN15-AR/EM-SX/SHF.01/strength - AN15-AR/EM-COR/SHF.01-CDLH.01/strength - AN15-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_126 x_pos: AN15-AR/DG-EPOS/BPM.03/x_pos y_pos: AN15-AR/DG-EPOS/BPM.03/y_pos @@ -5976,7 +5976,7 @@ physics: - AN15-AR/EM-SX/SCF.02/strength - AN15-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_127 x_pos: AN15-AR/DG-EPOS/BPM.04/x_pos y_pos: AN15-AR/DG-EPOS/BPM.04/y_pos @@ -6036,7 +6036,7 @@ physics: - AN15-AR/EM-SX/SCF.05/strength - AN15-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_128 x_pos: AN15-AR/DG-EPOS/BPM.05/x_pos y_pos: AN15-AR/DG-EPOS/BPM.05/y_pos @@ -6096,7 +6096,7 @@ physics: - AN15-AR/EM-SX/SCF.08/strength - AN15-AR/EM-COR/SCF.08-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_129 x_pos: AN15-AR/DG-EPOS/BPM.06/x_pos y_pos: AN15-AR/DG-EPOS/BPM.06/y_pos @@ -6156,7 +6156,7 @@ - AN15-AR/EM-OCT/OC.04/strength - AN15-AR/EM-COR/OC.04-CQLN.13/strength - AN15-AR/EM-COR/OC.04-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_130 x_pos: AN15-AR/DG-EPOS/BPM.07/x_pos y_pos: AN15-AR/DG-EPOS/BPM.07/y_pos @@ -6216,7 +6216,7 @@ - AN15-AR/EM-OCT/OC.05/strength - AN15-AR/EM-COR/OC.05-CQLN.16/strength - AN15-AR/EM-COR/OC.05-CQLT.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_131 x_pos: AN15-AR/DG-EPOS/BPM.08/x_pos y_pos: AN15-AR/DG-EPOS/BPM.08/y_pos @@ -6288,7 +6288,7 @@ physics: - AN15-AR/EM-SX/SCD.18/strength - AN15-AR/EM-COR/SCD.18-CDLV.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_132 x_pos: AN15-AR/DG-EPOS/BPM.09/x_pos y_pos: AN15-AR/DG-EPOS/BPM.09/y_pos @@ -6310,7 +6310,7 @@ physics: - AN15-AR/EM-SX/SHD.04/strength - AN15-AR/EM-COR/SHD.04-CDLV.09/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_133 x_pos: AN15-AR/DG-EPOS/BPM.10/x_pos y_pos: AN15-AR/DG-EPOS/BPM.10/y_pos @@ -6364,11 +6364,11 @@ physics: - AN15-AR/EM-OCT/OH.04/strength - AN15-AR/EM-COR/OH.04-CQLN.22/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_134 x_pos: AN16-SD/DG-EPOS/BPM.01/x_pos y_pos: AN16-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_135 x_pos: AN16-SD/DG-EPOS/BPM.02/x_pos y_pos: AN16-SD/DG-EPOS/BPM.02/y_pos @@ -6424,11 +6424,11 @@ physics: - AN16-SD/EM-OCT/OH.02/strength - AN16-SD/EM-COR/OH.02-CQLN.02/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_136 x_pos: AN16-SD/DG-EPOS/BPM.03/x_pos y_pos: AN16-SD/DG-EPOS/BPM.03/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_137 x_pos: AN16-SD/DG-EPOS/BPM.04/x_pos y_pos: AN16-SD/DG-EPOS/BPM.04/y_pos @@ -6482,7 +6482,7 @@ physics: - AN16-AR/EM-SX/SHD.02/strength - AN16-AR/EM-COR/SHD.02-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_138 x_pos: AN16-AR/DG-EPOS/BPM.05/x_pos y_pos: AN16-AR/DG-EPOS/BPM.05/y_pos @@ -6536,7 +6536,7 @@ physics: - AN16-AR/EM-SX/SCF.02/strength - AN16-AR/EM-COR/SCF.02-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_139 x_pos: AN16-AR/DG-EPOS/BPM.06/x_pos y_pos: AN16-AR/DG-EPOS/BPM.06/y_pos @@ -6596,7 +6596,7 @@ physics: - AN16-AR/EM-SX/SCF.05/strength - AN16-AR/EM-COR/SCF.05-CDLH.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_140 x_pos: AN16-AR/DG-EPOS/BPM.07/x_pos y_pos: AN16-AR/DG-EPOS/BPM.07/y_pos @@ -6656,7 +6656,7 @@ physics: - AN16-AR/EM-SX/SCF.08/strength - AN16-AR/EM-COR/SCF.08-CDLH.07/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_141 x_pos: AN16-AR/DG-EPOS/BPM.08/x_pos y_pos: AN16-AR/DG-EPOS/BPM.08/y_pos @@ -6716,7 +6716,7 @@ - AN16-AR/EM-OCT/OC.04/strength - AN16-AR/EM-COR/OC.04-CQLN.13/strength - AN16-AR/EM-COR/OC.04-CQLT.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_142 x_pos: AN16-AR/DG-EPOS/BPM.09/x_pos y_pos: AN16-AR/DG-EPOS/BPM.09/y_pos @@ -6776,7 +6776,7 @@ - AN16-AR/EM-OCT/OC.05/strength - AN16-AR/EM-COR/OC.05-CQLN.16/strength - AN16-AR/EM-COR/OC.05-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_143 x_pos: AN16-AR/DG-EPOS/BPM.10/x_pos y_pos: AN16-AR/DG-EPOS/BPM.10/y_pos @@ -6848,7 +6848,7 @@ physics: - AN16-AR/EM-SX/SCD.18/strength - AN16-AR/EM-COR/SCD.18-CDLV.10/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_144 x_pos: AN16-AR/DG-EPOS/BPM.11/x_pos y_pos: AN16-AR/DG-EPOS/BPM.11/y_pos @@ -6890,7 +6890,7 @@ physics: - AN16-AR/EM-OCT/OH.03/strength - AN16-AR/EM-COR/OH.03-CQLN.21/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_145 x_pos: AN16-AR/DG-EPOS/BPM.12/x_pos y_pos: AN16-AR/DG-EPOS/BPM.12/y_pos @@ -6922,11 +6922,11 @@ - AN16-AR/EM-OCT/OH.04/strength - AN16-AR/EM-COR/OH.04-CQLN.22/strength - AN16-AR/EM-COR/OH.04-CQLT.07/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_146 x_pos: AN17-SD/DG-EPOS/BPM.01/x_pos y_pos: AN17-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_147 x_pos: AN17-SD/DG-EPOS/BPM.02/x_pos y_pos: AN17-SD/DG-EPOS/BPM.02/y_pos @@ -6958,7 +6958,7 @@ - AN17-AR/EM-SX/SHF.01/strength - AN17-AR/EM-COR/SHF.01-CDLH.01/strength - AN17-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_148 x_pos: AN17-AR/DG-EPOS/BPM.03/x_pos y_pos: AN17-AR/DG-EPOS/BPM.03/y_pos @@ -7032,7 +7032,7 @@ physics: - AN17-AR/EM-SX/SCF.02/strength - AN17-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_149 x_pos: AN17-AR/DG-EPOS/BPM.04/x_pos y_pos: AN17-AR/DG-EPOS/BPM.04/y_pos @@ -7092,7 +7092,7 @@ physics: - AN17-AR/EM-SX/SCF.05/strength - AN17-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_150 x_pos: AN17-AR/DG-EPOS/BPM.05/x_pos y_pos: AN17-AR/DG-EPOS/BPM.05/y_pos @@ -7164,7 +7164,7 @@ physics: - AN17-AR/EM-SX/SCD.09/strength - AN17-AR/EM-COR/SCD.09-CDLV.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_151 x_pos: AN17-AR/DG-EPOS/BPM.06/x_pos y_pos: AN17-AR/DG-EPOS/BPM.06/y_pos @@ -7200,7 +7200,7 @@ type: pyaml.magnet.identity_model unit: 1/m physics: AN17-AR/EM-COR/CQLN.12/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_152 x_pos: AN17-AR/DG-EPOS/BPM.07/x_pos y_pos: AN17-AR/DG-EPOS/BPM.07/y_pos @@ -7232,11 +7232,11 @@ - AN17-AR/EM-OCT/OH.03/strength - AN17-AR/EM-COR/OH.03-CQLN.13/strength - AN17-AR/EM-COR/OH.03-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_153 x_pos: AN18-SD/DG-EPOS/BPM.01/x_pos y_pos: AN18-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_154 x_pos: AN18-SD/DG-EPOS/BPM.02/x_pos y_pos: AN18-SD/DG-EPOS/BPM.02/y_pos @@ -7268,7 +7268,7 @@ - AN18-AR/EM-SX/SHF.01/strength - AN18-AR/EM-COR/SHF.01-CDLH.01/strength - AN18-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_155 x_pos: AN18-AR/DG-EPOS/BPM.03/x_pos y_pos: AN18-AR/DG-EPOS/BPM.03/y_pos @@ -7336,7 +7336,7 @@ physics: - AN18-AR/EM-SX/SCF.02/strength - AN18-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_156 x_pos: AN18-AR/DG-EPOS/BPM.04/x_pos y_pos: AN18-AR/DG-EPOS/BPM.04/y_pos @@ -7396,7 +7396,7 @@ physics: - AN18-AR/EM-SX/SCF.05/strength - AN18-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_157 x_pos: AN18-AR/DG-EPOS/BPM.05/x_pos y_pos: AN18-AR/DG-EPOS/BPM.05/y_pos @@ -7456,7 +7456,7 @@ physics: - AN18-AR/EM-SX/SCF.08/strength - AN18-AR/EM-COR/SCF.08-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_158 x_pos: AN18-AR/DG-EPOS/BPM.06/x_pos y_pos: AN18-AR/DG-EPOS/BPM.06/y_pos @@ -7516,7 +7516,7 @@ - AN18-AR/EM-OCT/OC.04/strength - AN18-AR/EM-COR/OC.04-CQLN.13/strength - AN18-AR/EM-COR/OC.04-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_159 x_pos: AN18-AR/DG-EPOS/BPM.07/x_pos y_pos: AN18-AR/DG-EPOS/BPM.07/y_pos @@ -7576,7 +7576,7 @@ - AN18-AR/EM-OCT/OC.05/strength - AN18-AR/EM-COR/OC.05-CQLN.16/strength - AN18-AR/EM-COR/OC.05-CQLT.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_160 x_pos: AN18-AR/DG-EPOS/BPM.08/x_pos y_pos: AN18-AR/DG-EPOS/BPM.08/y_pos @@ -7648,7 +7648,7 @@ physics: - AN18-AR/EM-SX/SCD.18/strength - AN18-AR/EM-COR/SCD.18-CDLV.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_161 x_pos: AN18-AR/DG-EPOS/BPM.09/x_pos y_pos: AN18-AR/DG-EPOS/BPM.09/y_pos @@ -7684,7 +7684,7 @@ type: pyaml.magnet.identity_model unit: 1/m physics: AN18-AR/EM-COR/CQLN.21/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_162 x_pos: AN18-AR/DG-EPOS/BPM.10/x_pos y_pos: AN18-AR/DG-EPOS/BPM.10/y_pos @@ -7716,11 +7716,11 @@ - AN18-AR/EM-OCT/OH.02/strength - AN18-AR/EM-COR/OH.02-CQLN.22/strength - AN18-AR/EM-COR/OH.02-CQLT.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_163 x_pos: AN19-SD/DG-EPOS/BPM.01/x_pos y_pos: AN19-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_164 x_pos: AN19-SD/DG-EPOS/BPM.02/x_pos y_pos: AN19-SD/DG-EPOS/BPM.02/y_pos @@ -7752,7 +7752,7 @@ - AN19-AR/EM-SX/SHF.01/strength - AN19-AR/EM-COR/SHF.01-CDLH.01/strength - AN19-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_165 x_pos: AN19-AR/DG-EPOS/BPM.03/x_pos y_pos: AN19-AR/DG-EPOS/BPM.03/y_pos @@ -7820,7 +7820,7 @@ physics: - AN19-AR/EM-SX/SCF.02/strength - AN19-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_166 x_pos: AN19-AR/DG-EPOS/BPM.04/x_pos y_pos: AN19-AR/DG-EPOS/BPM.04/y_pos @@ -7880,7 +7880,7 @@ - AN19-AR/EM-OCT/OC.02/strength - AN19-AR/EM-COR/OC.02-CQLN.07/strength - AN19-AR/EM-COR/OC.02-CQLT.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_167 x_pos: AN19-AR/DG-EPOS/BPM.05/x_pos y_pos: AN19-AR/DG-EPOS/BPM.05/y_pos @@ -7952,7 +7952,7 @@ physics: - AN19-AR/EM-SX/SCD.09/strength - AN19-AR/EM-COR/SCD.09-CDLV.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_168 x_pos: AN19-AR/DG-EPOS/BPM.06/x_pos y_pos: AN19-AR/DG-EPOS/BPM.06/y_pos @@ -7994,7 +7994,7 @@ physics: - AN19-AR/EM-OCT/OH.02/strength - AN19-AR/EM-COR/OH.02-CQLN.12/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_169 x_pos: AN19-AR/DG-EPOS/BPM.07/x_pos y_pos: AN19-AR/DG-EPOS/BPM.07/y_pos @@ -8026,11 +8026,11 @@ - AN19-AR/EM-OCT/OH.03/strength - AN19-AR/EM-COR/OH.03-CQLN.13/strength - AN19-AR/EM-COR/OH.03-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_170 x_pos: AN20-SD/DG-EPOS/BPM.01/x_pos y_pos: AN20-SD/DG-EPOS/BPM.01/y_pos -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_171 x_pos: AN20-SD/DG-EPOS/BPM.02/x_pos y_pos: AN20-SD/DG-EPOS/BPM.02/y_pos @@ -8062,7 +8062,7 @@ - AN20-AR/EM-SX/SHF.01/strength - AN20-AR/EM-COR/SHF.01-CDLH.01/strength - AN20-AR/EM-COR/SHF.01-CDLV.01/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_172 x_pos: AN20-AR/DG-EPOS/BPM.03/x_pos y_pos: AN20-AR/DG-EPOS/BPM.03/y_pos @@ -8136,7 +8136,7 @@ physics: - AN20-AR/EM-SX/SCF.02/strength - AN20-AR/EM-COR/SCF.02-CDLH.03/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_173 x_pos: AN20-AR/DG-EPOS/BPM.04/x_pos y_pos: AN20-AR/DG-EPOS/BPM.04/y_pos @@ -8196,7 +8196,7 @@ physics: - AN20-AR/EM-SX/SCF.05/strength - AN20-AR/EM-COR/SCF.05-CDLH.04/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_174 x_pos: AN20-AR/DG-EPOS/BPM.05/x_pos y_pos: AN20-AR/DG-EPOS/BPM.05/y_pos @@ -8256,7 +8256,7 @@ physics: - AN20-AR/EM-SX/SCF.08/strength - AN20-AR/EM-COR/SCF.08-CDLH.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_175 x_pos: AN20-AR/DG-EPOS/BPM.06/x_pos y_pos: AN20-AR/DG-EPOS/BPM.06/y_pos @@ -8316,7 +8316,7 @@ - AN20-AR/EM-OCT/OC.04/strength - AN20-AR/EM-COR/OC.04-CQLN.13/strength - AN20-AR/EM-COR/OC.04-CQLT.05/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_176 x_pos: AN20-AR/DG-EPOS/BPM.07/x_pos y_pos: AN20-AR/DG-EPOS/BPM.07/y_pos @@ -8376,7 +8376,7 @@ - AN20-AR/EM-OCT/OC.05/strength - AN20-AR/EM-COR/OC.05-CQLN.16/strength - AN20-AR/EM-COR/OC.05-CQLT.06/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_177 x_pos: AN20-AR/DG-EPOS/BPM.08/x_pos y_pos: AN20-AR/DG-EPOS/BPM.08/y_pos @@ -8448,7 +8448,7 @@ physics: - AN20-AR/EM-SX/SCD.18/strength - AN20-AR/EM-COR/SCD.18-CDLV.08/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_178 x_pos: AN20-AR/DG-EPOS/BPM.09/x_pos y_pos: AN20-AR/DG-EPOS/BPM.09/y_pos @@ -8470,7 +8470,7 @@ physics: - AN20-AR/EM-SX/SHD.04/strength - AN20-AR/EM-COR/SHD.04-CDLV.09/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_179 x_pos: AN20-AR/DG-EPOS/BPM.10/x_pos y_pos: AN20-AR/DG-EPOS/BPM.10/y_pos @@ -8526,7 +8526,7 @@ - AN20-AR/EM-SX/SHF.06/strength - AN20-AR/EM-COR/SHF.06-CDLH.10/strength - AN20-AR/EM-COR/SHF.06-CDLV.10/strength -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_180 x_pos: AN01-SD/DG-EPOS/BPM.01/x_pos y_pos: AN01-SD/DG-EPOS/BPM.01/y_pos diff --git a/pyaml/apidoc/gen_api.py b/pyaml/apidoc/gen_api.py index e2b05a8a..4439e85e 100644 --- a/pyaml/apidoc/gen_api.py +++ b/pyaml/apidoc/gen_api.py @@ -20,7 +20,6 @@ "pyaml.arrays.magnet_array", "pyaml.arrays.serialized_magnet", "pyaml.arrays.serialized_magnet_array", - "pyaml.bpm.bpm", "pyaml.common.abstract", "pyaml.common.abstract_aggregator", "pyaml.common.constants", @@ -40,6 +39,7 @@ "pyaml.control.deviceaccesslist", "pyaml.control.readback_value", "pyaml.diagnostics.atune_monitor", + "pyaml.diagnostics.bpm.bpm", "pyaml.diagnostics.tune_monitor", "pyaml.external.pySC_interface", "pyaml.lattice.abstract_impl", diff --git a/pyaml/arrays/bpm_array.py b/pyaml/arrays/bpm_array.py index 9006af38..f3751226 100644 --- a/pyaml/arrays/bpm_array.py +++ b/pyaml/arrays/bpm_array.py @@ -6,7 +6,8 @@ import numpy as np -from ..bpm.bpm import BPM +from pyaml.diagnostics.bpm import BPM + from ..common.abstract import ReadFloatArray from ..control.deviceaccesslist import DeviceAccessList from .element_array import ElementArray @@ -24,7 +25,7 @@ class RWBPMPosition(ReadFloatArray): ---------- name : str Name of the position accessor - bpms : list[pyaml.bpm.bpm.BPM] + bpms : list[pyaml.diagnostics.bpm.bpm.BPM] List of BPM objects to access Methods @@ -98,7 +99,7 @@ class RWBPMSinglePosition(ReadFloatArray): ---------- name : str Name of the position accessor - bpms : list[pyaml.bpm.bpm.BPM] + bpms : list[pyaml.diagnostics.bpm.bpm.BPM] List of BPM objects to access idx : int Index for the position axis (0 for horizontal, 1 for vertical) @@ -170,7 +171,7 @@ class BPMArray(ElementArray): ---------- arrayName : str Array name - bpms : list[pyaml.bpm.bpm.BPM] + bpms : list[pyaml.diagnostics.bpm.bpm.BPM] BPM list, all elements must be attached to the same instance of either a (:py:class:`~pyaml.lattice.simulator.Simulator` or a :py:class:`~pyaml.control.controlsystem.ControlSystem`). diff --git a/pyaml/arrays/element_array.py b/pyaml/arrays/element_array.py index 797fc919..d39d7cbb 100644 --- a/pyaml/arrays/element_array.py +++ b/pyaml/arrays/element_array.py @@ -10,7 +10,8 @@ import numpy as np -from ..bpm.bpm import BPM +from pyaml.diagnostics.bpm import BPM + from ..common.element import Element, __pyaml_repr__ from ..common.exception import PyAMLException from ..common.name_matching import resolve_names diff --git a/pyaml/bpm/__init__.py b/pyaml/bpm/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pyaml/common/holders/element_holder.py b/pyaml/common/holders/element_holder.py index f5880dd2..1cf63dfb 100644 --- a/pyaml/common/holders/element_holder.py +++ b/pyaml/common/holders/element_holder.py @@ -3,8 +3,9 @@ from abc import ABCMeta, abstractmethod from typing import TYPE_CHECKING, overload +from pyaml.diagnostics.bpm import BPM + from ...arrays.element_array import ElementArray -from ...bpm.bpm import BPM from ...diagnostics.tune_monitor import BetatronTuneMonitor from ...magnet.cfm_magnet import CombinedFunctionMagnet from ...magnet.magnet import Magnet diff --git a/pyaml/common/holders/sub_holders.py b/pyaml/common/holders/sub_holders.py index f5e36b92..7d2bdc0a 100644 --- a/pyaml/common/holders/sub_holders.py +++ b/pyaml/common/holders/sub_holders.py @@ -2,11 +2,12 @@ from typing import TYPE_CHECKING +from pyaml.diagnostics.bpm import BPM + from ...arrays.bpm_array import BPMArray from ...arrays.cfm_magnet_array import CombinedFunctionMagnetArray from ...arrays.magnet_array import MagnetArray from ...arrays.serialized_magnet_array import SerializedMagnetsArray -from ...bpm.bpm import BPM from ...magnet.cfm_magnet import CombinedFunctionMagnet from ...magnet.magnet import Magnet from ...magnet.serialized_magnet import SerializedMagnets diff --git a/pyaml/control/controlsystem.py b/pyaml/control/controlsystem.py index d2e25470..d8a5ef80 100644 --- a/pyaml/control/controlsystem.py +++ b/pyaml/control/controlsystem.py @@ -10,7 +10,8 @@ from pydantic import BaseModel -from ..bpm.bpm import BPM +from pyaml.diagnostics.bpm import BPM + from ..common.abstract import RWMapper from ..common.abstract_aggregator import ScalarAggregator from ..common.exception import PyAMLException diff --git a/pyaml/diagnostics/bpm/__init__.py b/pyaml/diagnostics/bpm/__init__.py new file mode 100644 index 00000000..84915bda --- /dev/null +++ b/pyaml/diagnostics/bpm/__init__.py @@ -0,0 +1,3 @@ +from pyaml.diagnostics.bpm.bpm import BPM + +__all__ = ["BPM"] diff --git a/pyaml/bpm/bpm.py b/pyaml/diagnostics/bpm/bpm.py similarity index 94% rename from pyaml/bpm/bpm.py rename to pyaml/diagnostics/bpm/bpm.py index 364f3899..c63f662a 100644 --- a/pyaml/bpm/bpm.py +++ b/pyaml/diagnostics/bpm/bpm.py @@ -3,13 +3,13 @@ import copy from typing import TYPE_CHECKING, Self -from ..common.abstract import ReadFloatArray, ReadWriteFloatArray, ReadWriteFloatScalar -from ..common.element import Element, __pyaml_repr__ -from ..common.exception import PyAMLException -from ..validation import DynamicValidation, register_schema +from pyaml.common.abstract import ReadFloatArray, ReadWriteFloatArray, ReadWriteFloatScalar +from pyaml.common.element import Element, __pyaml_repr__ +from pyaml.common.exception import PyAMLException +from pyaml.validation import DynamicValidation, register_schema if TYPE_CHECKING: - from ..common.holders.element_holder import ElementHolder + from pyaml.common.holders.element_holder import ElementHolder PYAMLCLASS = "BPM" @@ -66,7 +66,7 @@ class BPM(Element, DynamicValidation): ------------- .. code-block:: yaml - - type: pyaml.bpm.bpm + - type: pyaml.diagnostics.bpm.bpm name: BPM_NAME lattice_names: LATTICE_BPM_NAME x_pos: DEVICE/X_POSITION diff --git a/pyaml/lattice/simulator.py b/pyaml/lattice/simulator.py index 14903724..738ddaf2 100644 --- a/pyaml/lattice/simulator.py +++ b/pyaml/lattice/simulator.py @@ -10,7 +10,8 @@ import at -from ..bpm.bpm import BPM +from pyaml.diagnostics.bpm import BPM + from ..common.abstract_aggregator import ScalarAggregator from ..common.element import Element, __pyaml_repr__ from ..common.exception import PyAMLException diff --git a/pyaml/tuning_tools/bba.py b/pyaml/tuning_tools/bba.py index 05eb9b8e..feb8bccd 100644 --- a/pyaml/tuning_tools/bba.py +++ b/pyaml/tuning_tools/bba.py @@ -23,8 +23,9 @@ from .measurement_tool import MeasurementTool if TYPE_CHECKING: + from pyaml.diagnostics.bpm import BPM + from ..arrays.bpm_array import BPMArray - from ..bpm.bpm import BPM from ..magnet.magnet import Magnet logger = logging.getLogger(__name__) diff --git a/pyaml/tuning_tools/bba2.py b/pyaml/tuning_tools/bba2.py index 1e34d80d..adaa9224 100644 --- a/pyaml/tuning_tools/bba2.py +++ b/pyaml/tuning_tools/bba2.py @@ -18,8 +18,9 @@ from .measurement_tool import MeasurementTool if TYPE_CHECKING: + from pyaml.diagnostics.bpm import BPM + from ..arrays.bpm_array import BPMArray - from ..bpm.bpm import BPM from ..magnet.magnet import Magnet from .tune import Tune diff --git a/tests/common/test_element_holder_collection.py b/tests/common/test_element_holder_collection.py index a90a1523..01585cf9 100644 --- a/tests/common/test_element_holder_collection.py +++ b/tests/common/test_element_holder_collection.py @@ -3,8 +3,8 @@ from pyaml.arrays.bpm_array import BPMArray from pyaml.arrays.element_array import ElementArray from pyaml.arrays.magnet_array import MagnetArray -from pyaml.bpm.bpm import BPM from pyaml.common.exception import PyAMLException +from pyaml.diagnostics.bpm.bpm import BPM from pyaml.lattice.simulator import Simulator diff --git a/tests/common/test_errors.py b/tests/common/test_errors.py index 4265a20c..8e970f58 100644 --- a/tests/common/test_errors.py +++ b/tests/common/test_errors.py @@ -50,12 +50,12 @@ def test_tune(install_test_package): def test_duplicate_error_reports_source_line_and_column_across_files(tmp_path): devices_a = tmp_path / "devices_a.yaml" devices_a.write_text( - "- type: pyaml.bpm.bpm\n name: BPM_DUPLICATE\n", + "- type: pyaml.diagnostics.bpm.bpm\n name: BPM_DUPLICATE\n", encoding="utf-8", ) devices_b = tmp_path / "devices_b.yaml" devices_b.write_text( - "- type: pyaml.bpm.bpm\n name: BPM_UNIQUE\n- type: pyaml.bpm.bpm\n name: BPM_DUPLICATE\n", + "- type: pyaml.diagnostics.bpm.bpm\n name: BPM_UNIQUE\n- type: pyaml.diagnostics.bpm.bpm\n name: BPM_DUPLICATE\n", encoding="utf-8", ) root = tmp_path / "root.yaml" @@ -76,7 +76,7 @@ def test_duplicate_error_reports_source_line_and_column_across_files(tmp_path): def test_malformed_local_included_yaml_reports_source_line_and_column(tmp_path): broken_devices = tmp_path / "broken_devices.yaml" broken_devices.write_text( - "- type: pyaml.bpm.bpm\n name: BPM_BROKEN\n model:\n type: [oops\n", + "- type: pyaml.diagnostics.bpm.bpm\n name: BPM_BROKEN\n model:\n type: [oops\n", encoding="utf-8", ) root = tmp_path / "root.yaml" @@ -101,7 +101,7 @@ def test_malformed_local_included_yaml_reports_source_line_and_column(tmp_path): def test_truncated_local_included_json_reports_source_and_position(tmp_path): broken_devices = tmp_path / "broken_devices.json" broken_devices.write_text( - '{"type": "pyaml.bpm.bpm", "name": "BPM_BROKEN", "model": ', + '{"type": "pyaml.diagnostics.bpm.bpm", "name": "BPM_BROKEN", "model": ', encoding="utf-8", ) root = tmp_path / "root.yaml" @@ -133,7 +133,9 @@ def test_malformed_remote_included_yaml_reports_source_line_and_column(http_conf "data_folder: /data/store\n" "devices: fragments/broken_devices.yaml\n" ), - "/configs/fragments/broken_devices.yaml": ("- type: pyaml.bpm.bpm\n name: BPM_BROKEN\n model:\n type: [oops\n"), + "/configs/fragments/broken_devices.yaml": ( + "- type: pyaml.diagnostics.bpm.bpm\n name: BPM_BROKEN\n model:\n type: [oops\n" + ), } with http_config_server(routes) as base_url: @@ -156,7 +158,7 @@ def test_truncated_remote_included_json_reports_source_and_position(http_config_ "devices: fragments/broken_devices.json\n" ), "/configs/fragments/broken_devices.json": ( - '{"type": "pyaml.bpm.bpm", "name": "BPM_BROKEN", "model": ', + '{"type": "pyaml.diagnostics.bpm.bpm", "name": "BPM_BROKEN", "model": ', "application/json", 200, ), diff --git a/tests/config/EBSOrbit.yaml b/tests/config/EBSOrbit.yaml index 5d6ea257..55ffe743 100644 --- a/tests/config/EBSOrbit.yaml +++ b/tests/config/EBSOrbit.yaml @@ -5713,1285 +5713,1285 @@ devices: - srmag/ps-corr-sh3/c03-e-ch3/current - srmag/ps-corr-sh3/c03-e-ch5/current - srmag/ps-corr-sh3/c03-e-ch6/current -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-01 x_pos: srdiag/bpm/c04-01/SA_HPosition y_pos: srdiag/bpm/c04-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-02 x_pos: srdiag/bpm/c04-02/SA_HPosition y_pos: srdiag/bpm/c04-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-03 x_pos: srdiag/bpm/c04-03/SA_HPosition y_pos: srdiag/bpm/c04-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-04 x_pos: srdiag/bpm/c04-04/SA_HPosition y_pos: srdiag/bpm/c04-04/SA_VPosition x_offset: srdiag/bpm/c04-04/HOffset y_offset: srdiag/bpm/c04-04/VOffset -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-05 x_pos: srdiag/bpm/c04-05/SA_HPosition y_pos: srdiag/bpm/c04-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-06 x_pos: srdiag/bpm/c04-06/SA_HPosition y_pos: srdiag/bpm/c04-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-07 x_pos: srdiag/bpm/c04-07/SA_HPosition y_pos: srdiag/bpm/c04-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-08 x_pos: srdiag/bpm/c04-08/SA_HPosition y_pos: srdiag/bpm/c04-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-09 x_pos: srdiag/bpm/c04-09/SA_HPosition y_pos: srdiag/bpm/c04-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-10 x_pos: srdiag/bpm/c04-10/SA_HPosition y_pos: srdiag/bpm/c04-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-01 x_pos: srdiag/bpm/c05-01/SA_HPosition y_pos: srdiag/bpm/c05-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-02 x_pos: srdiag/bpm/c05-02/SA_HPosition y_pos: srdiag/bpm/c05-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-03 x_pos: srdiag/bpm/c05-03/SA_HPosition y_pos: srdiag/bpm/c05-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-04 x_pos: srdiag/bpm/c05-04/SA_HPosition y_pos: srdiag/bpm/c05-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-05 x_pos: srdiag/bpm/c05-05/SA_HPosition y_pos: srdiag/bpm/c05-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-06 x_pos: srdiag/bpm/c05-06/SA_HPosition y_pos: srdiag/bpm/c05-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-07 x_pos: srdiag/bpm/c05-07/SA_HPosition y_pos: srdiag/bpm/c05-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-08 x_pos: srdiag/bpm/c05-08/SA_HPosition y_pos: srdiag/bpm/c05-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-09 x_pos: srdiag/bpm/c05-09/SA_HPosition y_pos: srdiag/bpm/c05-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C05-10 x_pos: srdiag/bpm/c05-10/SA_HPosition y_pos: srdiag/bpm/c05-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-01 x_pos: srdiag/bpm/c06-01/SA_HPosition y_pos: srdiag/bpm/c06-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-02 x_pos: srdiag/bpm/c06-02/SA_HPosition y_pos: srdiag/bpm/c06-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-03 x_pos: srdiag/bpm/c06-03/SA_HPosition y_pos: srdiag/bpm/c06-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-04 x_pos: srdiag/bpm/c06-04/SA_HPosition y_pos: srdiag/bpm/c06-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-05 x_pos: srdiag/bpm/c06-05/SA_HPosition y_pos: srdiag/bpm/c06-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-06 x_pos: srdiag/bpm/c06-06/SA_HPosition y_pos: srdiag/bpm/c06-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-07 x_pos: srdiag/bpm/c06-07/SA_HPosition y_pos: srdiag/bpm/c06-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-08 x_pos: srdiag/bpm/c06-08/SA_HPosition y_pos: srdiag/bpm/c06-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-09 x_pos: srdiag/bpm/c06-09/SA_HPosition y_pos: srdiag/bpm/c06-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C06-10 x_pos: srdiag/bpm/c06-10/SA_HPosition y_pos: srdiag/bpm/c06-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-01 x_pos: srdiag/bpm/c07-01/SA_HPosition y_pos: srdiag/bpm/c07-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-02 x_pos: srdiag/bpm/c07-02/SA_HPosition y_pos: srdiag/bpm/c07-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-03 x_pos: srdiag/bpm/c07-03/SA_HPosition y_pos: srdiag/bpm/c07-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-04 x_pos: srdiag/bpm/c07-04/SA_HPosition y_pos: srdiag/bpm/c07-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-05 x_pos: srdiag/bpm/c07-05/SA_HPosition y_pos: srdiag/bpm/c07-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-06 x_pos: srdiag/bpm/c07-06/SA_HPosition y_pos: srdiag/bpm/c07-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-07 x_pos: srdiag/bpm/c07-07/SA_HPosition y_pos: srdiag/bpm/c07-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-08 x_pos: srdiag/bpm/c07-08/SA_HPosition y_pos: srdiag/bpm/c07-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-09 x_pos: srdiag/bpm/c07-09/SA_HPosition y_pos: srdiag/bpm/c07-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C07-10 x_pos: srdiag/bpm/c07-10/SA_HPosition y_pos: srdiag/bpm/c07-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-01 x_pos: srdiag/bpm/c08-01/SA_HPosition y_pos: srdiag/bpm/c08-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-02 x_pos: srdiag/bpm/c08-02/SA_HPosition y_pos: srdiag/bpm/c08-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-03 x_pos: srdiag/bpm/c08-03/SA_HPosition y_pos: srdiag/bpm/c08-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-04 x_pos: srdiag/bpm/c08-04/SA_HPosition y_pos: srdiag/bpm/c08-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-05 x_pos: srdiag/bpm/c08-05/SA_HPosition y_pos: srdiag/bpm/c08-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-06 x_pos: srdiag/bpm/c08-06/SA_HPosition y_pos: srdiag/bpm/c08-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-07 x_pos: srdiag/bpm/c08-07/SA_HPosition y_pos: srdiag/bpm/c08-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-08 x_pos: srdiag/bpm/c08-08/SA_HPosition y_pos: srdiag/bpm/c08-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-09 x_pos: srdiag/bpm/c08-09/SA_HPosition y_pos: srdiag/bpm/c08-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C08-10 x_pos: srdiag/bpm/c08-10/SA_HPosition y_pos: srdiag/bpm/c08-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-01 x_pos: srdiag/bpm/c09-01/SA_HPosition y_pos: srdiag/bpm/c09-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-02 x_pos: srdiag/bpm/c09-02/SA_HPosition y_pos: srdiag/bpm/c09-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-03 x_pos: srdiag/bpm/c09-03/SA_HPosition y_pos: srdiag/bpm/c09-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-04 x_pos: srdiag/bpm/c09-04/SA_HPosition y_pos: srdiag/bpm/c09-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-05 x_pos: srdiag/bpm/c09-05/SA_HPosition y_pos: srdiag/bpm/c09-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-06 x_pos: srdiag/bpm/c09-06/SA_HPosition y_pos: srdiag/bpm/c09-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-07 x_pos: srdiag/bpm/c09-07/SA_HPosition y_pos: srdiag/bpm/c09-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-08 x_pos: srdiag/bpm/c09-08/SA_HPosition y_pos: srdiag/bpm/c09-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-09 x_pos: srdiag/bpm/c09-09/SA_HPosition y_pos: srdiag/bpm/c09-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C09-10 x_pos: srdiag/bpm/c09-10/SA_HPosition y_pos: srdiag/bpm/c09-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-01 x_pos: srdiag/bpm/c10-01/SA_HPosition y_pos: srdiag/bpm/c10-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-02 x_pos: srdiag/bpm/c10-02/SA_HPosition y_pos: srdiag/bpm/c10-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-03 x_pos: srdiag/bpm/c10-03/SA_HPosition y_pos: srdiag/bpm/c10-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-04 x_pos: srdiag/bpm/c10-04/SA_HPosition y_pos: srdiag/bpm/c10-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-05 x_pos: srdiag/bpm/c10-05/SA_HPosition y_pos: srdiag/bpm/c10-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-06 x_pos: srdiag/bpm/c10-06/SA_HPosition y_pos: srdiag/bpm/c10-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-07 x_pos: srdiag/bpm/c10-07/SA_HPosition y_pos: srdiag/bpm/c10-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-08 x_pos: srdiag/bpm/c10-08/SA_HPosition y_pos: srdiag/bpm/c10-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-09 x_pos: srdiag/bpm/c10-09/SA_HPosition y_pos: srdiag/bpm/c10-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C10-10 x_pos: srdiag/bpm/c10-10/SA_HPosition y_pos: srdiag/bpm/c10-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-01 x_pos: srdiag/bpm/c11-01/SA_HPosition y_pos: srdiag/bpm/c11-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-02 x_pos: srdiag/bpm/c11-02/SA_HPosition y_pos: srdiag/bpm/c11-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-03 x_pos: srdiag/bpm/c11-03/SA_HPosition y_pos: srdiag/bpm/c11-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-04 x_pos: srdiag/bpm/c11-04/SA_HPosition y_pos: srdiag/bpm/c11-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-05 x_pos: srdiag/bpm/c11-05/SA_HPosition y_pos: srdiag/bpm/c11-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-06 x_pos: srdiag/bpm/c11-06/SA_HPosition y_pos: srdiag/bpm/c11-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-07 x_pos: srdiag/bpm/c11-07/SA_HPosition y_pos: srdiag/bpm/c11-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-08 x_pos: srdiag/bpm/c11-08/SA_HPosition y_pos: srdiag/bpm/c11-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-09 x_pos: srdiag/bpm/c11-09/SA_HPosition y_pos: srdiag/bpm/c11-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C11-10 x_pos: srdiag/bpm/c11-10/SA_HPosition y_pos: srdiag/bpm/c11-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-01 x_pos: srdiag/bpm/c12-01/SA_HPosition y_pos: srdiag/bpm/c12-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-02 x_pos: srdiag/bpm/c12-02/SA_HPosition y_pos: srdiag/bpm/c12-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-03 x_pos: srdiag/bpm/c12-03/SA_HPosition y_pos: srdiag/bpm/c12-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-04 x_pos: srdiag/bpm/c12-04/SA_HPosition y_pos: srdiag/bpm/c12-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-05 x_pos: srdiag/bpm/c12-05/SA_HPosition y_pos: srdiag/bpm/c12-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-06 x_pos: srdiag/bpm/c12-06/SA_HPosition y_pos: srdiag/bpm/c12-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-07 x_pos: srdiag/bpm/c12-07/SA_HPosition y_pos: srdiag/bpm/c12-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-08 x_pos: srdiag/bpm/c12-08/SA_HPosition y_pos: srdiag/bpm/c12-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-09 x_pos: srdiag/bpm/c12-09/SA_HPosition y_pos: srdiag/bpm/c12-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C12-10 x_pos: srdiag/bpm/c12-10/SA_HPosition y_pos: srdiag/bpm/c12-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-01 x_pos: srdiag/bpm/c13-01/SA_HPosition y_pos: srdiag/bpm/c13-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-02 x_pos: srdiag/bpm/c13-02/SA_HPosition y_pos: srdiag/bpm/c13-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-03 x_pos: srdiag/bpm/c13-03/SA_HPosition y_pos: srdiag/bpm/c13-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-04 x_pos: srdiag/bpm/c13-04/SA_HPosition y_pos: srdiag/bpm/c13-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-05 x_pos: srdiag/bpm/c13-05/SA_HPosition y_pos: srdiag/bpm/c13-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-06 x_pos: srdiag/bpm/c13-06/SA_HPosition y_pos: srdiag/bpm/c13-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-07 x_pos: srdiag/bpm/c13-07/SA_HPosition y_pos: srdiag/bpm/c13-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-08 x_pos: srdiag/bpm/c13-08/SA_HPosition y_pos: srdiag/bpm/c13-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-09 x_pos: srdiag/bpm/c13-09/SA_HPosition y_pos: srdiag/bpm/c13-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C13-10 x_pos: srdiag/bpm/c13-10/SA_HPosition y_pos: srdiag/bpm/c13-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-01 x_pos: srdiag/bpm/c14-01/SA_HPosition y_pos: srdiag/bpm/c14-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-02 x_pos: srdiag/bpm/c14-02/SA_HPosition y_pos: srdiag/bpm/c14-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-03 x_pos: srdiag/bpm/c14-03/SA_HPosition y_pos: srdiag/bpm/c14-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-04 x_pos: srdiag/bpm/c14-04/SA_HPosition y_pos: srdiag/bpm/c14-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-05 x_pos: srdiag/bpm/c14-05/SA_HPosition y_pos: srdiag/bpm/c14-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-06 x_pos: srdiag/bpm/c14-06/SA_HPosition y_pos: srdiag/bpm/c14-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-07 x_pos: srdiag/bpm/c14-07/SA_HPosition y_pos: srdiag/bpm/c14-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-08 x_pos: srdiag/bpm/c14-08/SA_HPosition y_pos: srdiag/bpm/c14-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-09 x_pos: srdiag/bpm/c14-09/SA_HPosition y_pos: srdiag/bpm/c14-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C14-10 x_pos: srdiag/bpm/c14-10/SA_HPosition y_pos: srdiag/bpm/c14-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-01 x_pos: srdiag/bpm/c15-01/SA_HPosition y_pos: srdiag/bpm/c15-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-02 x_pos: srdiag/bpm/c15-02/SA_HPosition y_pos: srdiag/bpm/c15-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-03 x_pos: srdiag/bpm/c15-03/SA_HPosition y_pos: srdiag/bpm/c15-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-04 x_pos: srdiag/bpm/c15-04/SA_HPosition y_pos: srdiag/bpm/c15-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-05 x_pos: srdiag/bpm/c15-05/SA_HPosition y_pos: srdiag/bpm/c15-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-06 x_pos: srdiag/bpm/c15-06/SA_HPosition y_pos: srdiag/bpm/c15-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-07 x_pos: srdiag/bpm/c15-07/SA_HPosition y_pos: srdiag/bpm/c15-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-08 x_pos: srdiag/bpm/c15-08/SA_HPosition y_pos: srdiag/bpm/c15-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-09 x_pos: srdiag/bpm/c15-09/SA_HPosition y_pos: srdiag/bpm/c15-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C15-10 x_pos: srdiag/bpm/c15-10/SA_HPosition y_pos: srdiag/bpm/c15-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-01 x_pos: srdiag/bpm/c16-01/SA_HPosition y_pos: srdiag/bpm/c16-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-02 x_pos: srdiag/bpm/c16-02/SA_HPosition y_pos: srdiag/bpm/c16-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-03 x_pos: srdiag/bpm/c16-03/SA_HPosition y_pos: srdiag/bpm/c16-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-04 x_pos: srdiag/bpm/c16-04/SA_HPosition y_pos: srdiag/bpm/c16-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-05 x_pos: srdiag/bpm/c16-05/SA_HPosition y_pos: srdiag/bpm/c16-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-06 x_pos: srdiag/bpm/c16-06/SA_HPosition y_pos: srdiag/bpm/c16-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-07 x_pos: srdiag/bpm/c16-07/SA_HPosition y_pos: srdiag/bpm/c16-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-08 x_pos: srdiag/bpm/c16-08/SA_HPosition y_pos: srdiag/bpm/c16-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-09 x_pos: srdiag/bpm/c16-09/SA_HPosition y_pos: srdiag/bpm/c16-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C16-10 x_pos: srdiag/bpm/c16-10/SA_HPosition y_pos: srdiag/bpm/c16-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-01 x_pos: srdiag/bpm/c17-01/SA_HPosition y_pos: srdiag/bpm/c17-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-02 x_pos: srdiag/bpm/c17-02/SA_HPosition y_pos: srdiag/bpm/c17-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-03 x_pos: srdiag/bpm/c17-03/SA_HPosition y_pos: srdiag/bpm/c17-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-04 x_pos: srdiag/bpm/c17-04/SA_HPosition y_pos: srdiag/bpm/c17-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-05 x_pos: srdiag/bpm/c17-05/SA_HPosition y_pos: srdiag/bpm/c17-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-06 x_pos: srdiag/bpm/c17-06/SA_HPosition y_pos: srdiag/bpm/c17-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-07 x_pos: srdiag/bpm/c17-07/SA_HPosition y_pos: srdiag/bpm/c17-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-08 x_pos: srdiag/bpm/c17-08/SA_HPosition y_pos: srdiag/bpm/c17-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-09 x_pos: srdiag/bpm/c17-09/SA_HPosition y_pos: srdiag/bpm/c17-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C17-10 x_pos: srdiag/bpm/c17-10/SA_HPosition y_pos: srdiag/bpm/c17-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-01 x_pos: srdiag/bpm/c18-01/SA_HPosition y_pos: srdiag/bpm/c18-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-02 x_pos: srdiag/bpm/c18-02/SA_HPosition y_pos: srdiag/bpm/c18-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-03 x_pos: srdiag/bpm/c18-03/SA_HPosition y_pos: srdiag/bpm/c18-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-04 x_pos: srdiag/bpm/c18-04/SA_HPosition y_pos: srdiag/bpm/c18-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-05 x_pos: srdiag/bpm/c18-05/SA_HPosition y_pos: srdiag/bpm/c18-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-06 x_pos: srdiag/bpm/c18-06/SA_HPosition y_pos: srdiag/bpm/c18-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-07 x_pos: srdiag/bpm/c18-07/SA_HPosition y_pos: srdiag/bpm/c18-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-08 x_pos: srdiag/bpm/c18-08/SA_HPosition y_pos: srdiag/bpm/c18-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-09 x_pos: srdiag/bpm/c18-09/SA_HPosition y_pos: srdiag/bpm/c18-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C18-10 x_pos: srdiag/bpm/c18-10/SA_HPosition y_pos: srdiag/bpm/c18-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-01 x_pos: srdiag/bpm/c19-01/SA_HPosition y_pos: srdiag/bpm/c19-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-02 x_pos: srdiag/bpm/c19-02/SA_HPosition y_pos: srdiag/bpm/c19-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-03 x_pos: srdiag/bpm/c19-03/SA_HPosition y_pos: srdiag/bpm/c19-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-04 x_pos: srdiag/bpm/c19-04/SA_HPosition y_pos: srdiag/bpm/c19-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-05 x_pos: srdiag/bpm/c19-05/SA_HPosition y_pos: srdiag/bpm/c19-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-06 x_pos: srdiag/bpm/c19-06/SA_HPosition y_pos: srdiag/bpm/c19-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-07 x_pos: srdiag/bpm/c19-07/SA_HPosition y_pos: srdiag/bpm/c19-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-08 x_pos: srdiag/bpm/c19-08/SA_HPosition y_pos: srdiag/bpm/c19-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-09 x_pos: srdiag/bpm/c19-09/SA_HPosition y_pos: srdiag/bpm/c19-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C19-10 x_pos: srdiag/bpm/c19-10/SA_HPosition y_pos: srdiag/bpm/c19-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-01 x_pos: srdiag/bpm/c20-01/SA_HPosition y_pos: srdiag/bpm/c20-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-02 x_pos: srdiag/bpm/c20-02/SA_HPosition y_pos: srdiag/bpm/c20-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-03 x_pos: srdiag/bpm/c20-03/SA_HPosition y_pos: srdiag/bpm/c20-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-04 x_pos: srdiag/bpm/c20-04/SA_HPosition y_pos: srdiag/bpm/c20-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-05 x_pos: srdiag/bpm/c20-05/SA_HPosition y_pos: srdiag/bpm/c20-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-06 x_pos: srdiag/bpm/c20-06/SA_HPosition y_pos: srdiag/bpm/c20-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-07 x_pos: srdiag/bpm/c20-07/SA_HPosition y_pos: srdiag/bpm/c20-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-08 x_pos: srdiag/bpm/c20-08/SA_HPosition y_pos: srdiag/bpm/c20-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-09 x_pos: srdiag/bpm/c20-09/SA_HPosition y_pos: srdiag/bpm/c20-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C20-10 x_pos: srdiag/bpm/c20-10/SA_HPosition y_pos: srdiag/bpm/c20-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-01 x_pos: srdiag/bpm/c21-01/SA_HPosition y_pos: srdiag/bpm/c21-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-02 x_pos: srdiag/bpm/c21-02/SA_HPosition y_pos: srdiag/bpm/c21-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-03 x_pos: srdiag/bpm/c21-03/SA_HPosition y_pos: srdiag/bpm/c21-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-04 x_pos: srdiag/bpm/c21-04/SA_HPosition y_pos: srdiag/bpm/c21-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-05 x_pos: srdiag/bpm/c21-05/SA_HPosition y_pos: srdiag/bpm/c21-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-06 x_pos: srdiag/bpm/c21-06/SA_HPosition y_pos: srdiag/bpm/c21-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-07 x_pos: srdiag/bpm/c21-07/SA_HPosition y_pos: srdiag/bpm/c21-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-08 x_pos: srdiag/bpm/c21-08/SA_HPosition y_pos: srdiag/bpm/c21-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-09 x_pos: srdiag/bpm/c21-09/SA_HPosition y_pos: srdiag/bpm/c21-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C21-10 x_pos: srdiag/bpm/c21-10/SA_HPosition y_pos: srdiag/bpm/c21-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-01 x_pos: srdiag/bpm/c22-01/SA_HPosition y_pos: srdiag/bpm/c22-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-02 x_pos: srdiag/bpm/c22-02/SA_HPosition y_pos: srdiag/bpm/c22-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-03 x_pos: srdiag/bpm/c22-03/SA_HPosition y_pos: srdiag/bpm/c22-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-04 x_pos: srdiag/bpm/c22-04/SA_HPosition y_pos: srdiag/bpm/c22-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-05 x_pos: srdiag/bpm/c22-05/SA_HPosition y_pos: srdiag/bpm/c22-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-06 x_pos: srdiag/bpm/c22-06/SA_HPosition y_pos: srdiag/bpm/c22-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-07 x_pos: srdiag/bpm/c22-07/SA_HPosition y_pos: srdiag/bpm/c22-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-08 x_pos: srdiag/bpm/c22-08/SA_HPosition y_pos: srdiag/bpm/c22-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-09 x_pos: srdiag/bpm/c22-09/SA_HPosition y_pos: srdiag/bpm/c22-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C22-10 x_pos: srdiag/bpm/c22-10/SA_HPosition y_pos: srdiag/bpm/c22-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-01 x_pos: srdiag/bpm/c23-01/SA_HPosition y_pos: srdiag/bpm/c23-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-02 x_pos: srdiag/bpm/c23-02/SA_HPosition y_pos: srdiag/bpm/c23-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-03 x_pos: srdiag/bpm/c23-03/SA_HPosition y_pos: srdiag/bpm/c23-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-04 x_pos: srdiag/bpm/c23-04/SA_HPosition y_pos: srdiag/bpm/c23-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-05 x_pos: srdiag/bpm/c23-05/SA_HPosition y_pos: srdiag/bpm/c23-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-06 x_pos: srdiag/bpm/c23-06/SA_HPosition y_pos: srdiag/bpm/c23-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-07 x_pos: srdiag/bpm/c23-07/SA_HPosition y_pos: srdiag/bpm/c23-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-08 x_pos: srdiag/bpm/c23-08/SA_HPosition y_pos: srdiag/bpm/c23-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-09 x_pos: srdiag/bpm/c23-09/SA_HPosition y_pos: srdiag/bpm/c23-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C23-10 x_pos: srdiag/bpm/c23-10/SA_HPosition y_pos: srdiag/bpm/c23-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-01 x_pos: srdiag/bpm/c24-01/SA_HPosition y_pos: srdiag/bpm/c24-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-02 x_pos: srdiag/bpm/c24-02/SA_HPosition y_pos: srdiag/bpm/c24-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-03 x_pos: srdiag/bpm/c24-03/SA_HPosition y_pos: srdiag/bpm/c24-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-04 x_pos: srdiag/bpm/c24-04/SA_HPosition y_pos: srdiag/bpm/c24-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-05 x_pos: srdiag/bpm/c24-05/SA_HPosition y_pos: srdiag/bpm/c24-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-06 x_pos: srdiag/bpm/c24-06/SA_HPosition y_pos: srdiag/bpm/c24-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-07 x_pos: srdiag/bpm/c24-07/SA_HPosition y_pos: srdiag/bpm/c24-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-08 x_pos: srdiag/bpm/c24-08/SA_HPosition y_pos: srdiag/bpm/c24-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-09 x_pos: srdiag/bpm/c24-09/SA_HPosition y_pos: srdiag/bpm/c24-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C24-10 x_pos: srdiag/bpm/c24-10/SA_HPosition y_pos: srdiag/bpm/c24-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-01 x_pos: srdiag/bpm/c25-01/SA_HPosition y_pos: srdiag/bpm/c25-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-02 x_pos: srdiag/bpm/c25-02/SA_HPosition y_pos: srdiag/bpm/c25-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-03 x_pos: srdiag/bpm/c25-03/SA_HPosition y_pos: srdiag/bpm/c25-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-04 x_pos: srdiag/bpm/c25-04/SA_HPosition y_pos: srdiag/bpm/c25-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-05 x_pos: srdiag/bpm/c25-05/SA_HPosition y_pos: srdiag/bpm/c25-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-06 x_pos: srdiag/bpm/c25-06/SA_HPosition y_pos: srdiag/bpm/c25-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-07 x_pos: srdiag/bpm/c25-07/SA_HPosition y_pos: srdiag/bpm/c25-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-08 x_pos: srdiag/bpm/c25-08/SA_HPosition y_pos: srdiag/bpm/c25-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-09 x_pos: srdiag/bpm/c25-09/SA_HPosition y_pos: srdiag/bpm/c25-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C25-10 x_pos: srdiag/bpm/c25-10/SA_HPosition y_pos: srdiag/bpm/c25-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-01 x_pos: srdiag/bpm/c26-01/SA_HPosition y_pos: srdiag/bpm/c26-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-02 x_pos: srdiag/bpm/c26-02/SA_HPosition y_pos: srdiag/bpm/c26-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-03 x_pos: srdiag/bpm/c26-03/SA_HPosition y_pos: srdiag/bpm/c26-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-04 x_pos: srdiag/bpm/c26-04/SA_HPosition y_pos: srdiag/bpm/c26-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-05 x_pos: srdiag/bpm/c26-05/SA_HPosition y_pos: srdiag/bpm/c26-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-06 x_pos: srdiag/bpm/c26-06/SA_HPosition y_pos: srdiag/bpm/c26-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-07 x_pos: srdiag/bpm/c26-07/SA_HPosition y_pos: srdiag/bpm/c26-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-08 x_pos: srdiag/bpm/c26-08/SA_HPosition y_pos: srdiag/bpm/c26-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-09 x_pos: srdiag/bpm/c26-09/SA_HPosition y_pos: srdiag/bpm/c26-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C26-10 x_pos: srdiag/bpm/c26-10/SA_HPosition y_pos: srdiag/bpm/c26-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-01 x_pos: srdiag/bpm/c27-01/SA_HPosition y_pos: srdiag/bpm/c27-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-02 x_pos: srdiag/bpm/c27-02/SA_HPosition y_pos: srdiag/bpm/c27-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-03 x_pos: srdiag/bpm/c27-03/SA_HPosition y_pos: srdiag/bpm/c27-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-04 x_pos: srdiag/bpm/c27-04/SA_HPosition y_pos: srdiag/bpm/c27-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-05 x_pos: srdiag/bpm/c27-05/SA_HPosition y_pos: srdiag/bpm/c27-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-06 x_pos: srdiag/bpm/c27-06/SA_HPosition y_pos: srdiag/bpm/c27-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-07 x_pos: srdiag/bpm/c27-07/SA_HPosition y_pos: srdiag/bpm/c27-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-08 x_pos: srdiag/bpm/c27-08/SA_HPosition y_pos: srdiag/bpm/c27-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-09 x_pos: srdiag/bpm/c27-09/SA_HPosition y_pos: srdiag/bpm/c27-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C27-10 x_pos: srdiag/bpm/c27-10/SA_HPosition y_pos: srdiag/bpm/c27-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-01 x_pos: srdiag/bpm/c28-01/SA_HPosition y_pos: srdiag/bpm/c28-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-02 x_pos: srdiag/bpm/c28-02/SA_HPosition y_pos: srdiag/bpm/c28-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-03 x_pos: srdiag/bpm/c28-03/SA_HPosition y_pos: srdiag/bpm/c28-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-04 x_pos: srdiag/bpm/c28-04/SA_HPosition y_pos: srdiag/bpm/c28-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-05 x_pos: srdiag/bpm/c28-05/SA_HPosition y_pos: srdiag/bpm/c28-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-06 x_pos: srdiag/bpm/c28-06/SA_HPosition y_pos: srdiag/bpm/c28-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-07 x_pos: srdiag/bpm/c28-07/SA_HPosition y_pos: srdiag/bpm/c28-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-08 x_pos: srdiag/bpm/c28-08/SA_HPosition y_pos: srdiag/bpm/c28-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-09 x_pos: srdiag/bpm/c28-09/SA_HPosition y_pos: srdiag/bpm/c28-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C28-10 x_pos: srdiag/bpm/c28-10/SA_HPosition y_pos: srdiag/bpm/c28-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-01 x_pos: srdiag/bpm/c29-01/SA_HPosition y_pos: srdiag/bpm/c29-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-02 x_pos: srdiag/bpm/c29-02/SA_HPosition y_pos: srdiag/bpm/c29-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-03 x_pos: srdiag/bpm/c29-03/SA_HPosition y_pos: srdiag/bpm/c29-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-04 x_pos: srdiag/bpm/c29-04/SA_HPosition y_pos: srdiag/bpm/c29-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-05 x_pos: srdiag/bpm/c29-05/SA_HPosition y_pos: srdiag/bpm/c29-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-06 x_pos: srdiag/bpm/c29-06/SA_HPosition y_pos: srdiag/bpm/c29-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-07 x_pos: srdiag/bpm/c29-07/SA_HPosition y_pos: srdiag/bpm/c29-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-08 x_pos: srdiag/bpm/c29-08/SA_HPosition y_pos: srdiag/bpm/c29-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-09 x_pos: srdiag/bpm/c29-09/SA_HPosition y_pos: srdiag/bpm/c29-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C29-10 x_pos: srdiag/bpm/c29-10/SA_HPosition y_pos: srdiag/bpm/c29-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-01 x_pos: srdiag/bpm/c30-01/SA_HPosition y_pos: srdiag/bpm/c30-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-02 x_pos: srdiag/bpm/c30-02/SA_HPosition y_pos: srdiag/bpm/c30-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-03 x_pos: srdiag/bpm/c30-03/SA_HPosition y_pos: srdiag/bpm/c30-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-04 x_pos: srdiag/bpm/c30-04/SA_HPosition y_pos: srdiag/bpm/c30-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-05 x_pos: srdiag/bpm/c30-05/SA_HPosition y_pos: srdiag/bpm/c30-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-06 x_pos: srdiag/bpm/c30-06/SA_HPosition y_pos: srdiag/bpm/c30-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-07 x_pos: srdiag/bpm/c30-07/SA_HPosition y_pos: srdiag/bpm/c30-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-08 x_pos: srdiag/bpm/c30-08/SA_HPosition y_pos: srdiag/bpm/c30-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-09 x_pos: srdiag/bpm/c30-09/SA_HPosition y_pos: srdiag/bpm/c30-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C30-10 x_pos: srdiag/bpm/c30-10/SA_HPosition y_pos: srdiag/bpm/c30-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-01 x_pos: srdiag/bpm/c31-01/SA_HPosition y_pos: srdiag/bpm/c31-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-02 x_pos: srdiag/bpm/c31-02/SA_HPosition y_pos: srdiag/bpm/c31-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-03 x_pos: srdiag/bpm/c31-03/SA_HPosition y_pos: srdiag/bpm/c31-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-04 x_pos: srdiag/bpm/c31-04/SA_HPosition y_pos: srdiag/bpm/c31-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-05 x_pos: srdiag/bpm/c31-05/SA_HPosition y_pos: srdiag/bpm/c31-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-06 x_pos: srdiag/bpm/c31-06/SA_HPosition y_pos: srdiag/bpm/c31-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-07 x_pos: srdiag/bpm/c31-07/SA_HPosition y_pos: srdiag/bpm/c31-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-08 x_pos: srdiag/bpm/c31-08/SA_HPosition y_pos: srdiag/bpm/c31-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-09 x_pos: srdiag/bpm/c31-09/SA_HPosition y_pos: srdiag/bpm/c31-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C31-10 x_pos: srdiag/bpm/c31-10/SA_HPosition y_pos: srdiag/bpm/c31-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-01 x_pos: srdiag/bpm/c32-01/SA_HPosition y_pos: srdiag/bpm/c32-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-02 x_pos: srdiag/bpm/c32-02/SA_HPosition y_pos: srdiag/bpm/c32-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-03 x_pos: srdiag/bpm/c32-03/SA_HPosition y_pos: srdiag/bpm/c32-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-04 x_pos: srdiag/bpm/c32-04/SA_HPosition y_pos: srdiag/bpm/c32-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-05 x_pos: srdiag/bpm/c32-05/SA_HPosition y_pos: srdiag/bpm/c32-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-06 x_pos: srdiag/bpm/c32-06/SA_HPosition y_pos: srdiag/bpm/c32-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-07 x_pos: srdiag/bpm/c32-07/SA_HPosition y_pos: srdiag/bpm/c32-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-08 x_pos: srdiag/bpm/c32-08/SA_HPosition y_pos: srdiag/bpm/c32-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-09 x_pos: srdiag/bpm/c32-09/SA_HPosition y_pos: srdiag/bpm/c32-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C32-10 x_pos: srdiag/bpm/c32-10/SA_HPosition y_pos: srdiag/bpm/c32-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-01 x_pos: srdiag/bpm/c01-01/SA_HPosition y_pos: srdiag/bpm/c01-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-02 x_pos: srdiag/bpm/c01-02/SA_HPosition y_pos: srdiag/bpm/c01-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-03 x_pos: srdiag/bpm/c01-03/SA_HPosition y_pos: srdiag/bpm/c01-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-04 x_pos: srdiag/bpm/c01-04/SA_HPosition y_pos: srdiag/bpm/c01-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-05 x_pos: srdiag/bpm/c01-05/SA_HPosition y_pos: srdiag/bpm/c01-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-06 x_pos: srdiag/bpm/c01-06/SA_HPosition y_pos: srdiag/bpm/c01-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-07 x_pos: srdiag/bpm/c01-07/SA_HPosition y_pos: srdiag/bpm/c01-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-08 x_pos: srdiag/bpm/c01-08/SA_HPosition y_pos: srdiag/bpm/c01-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-09 x_pos: srdiag/bpm/c01-09/SA_HPosition y_pos: srdiag/bpm/c01-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-10 x_pos: srdiag/bpm/c01-10/SA_HPosition y_pos: srdiag/bpm/c01-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-01 x_pos: srdiag/bpm/c02-01/SA_HPosition y_pos: srdiag/bpm/c02-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-02 x_pos: srdiag/bpm/c02-02/SA_HPosition y_pos: srdiag/bpm/c02-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-03 x_pos: srdiag/bpm/c02-03/SA_HPosition y_pos: srdiag/bpm/c02-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-04 x_pos: srdiag/bpm/c02-04/SA_HPosition y_pos: srdiag/bpm/c02-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-05 x_pos: srdiag/bpm/c02-05/SA_HPosition y_pos: srdiag/bpm/c02-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-06 x_pos: srdiag/bpm/c02-06/SA_HPosition y_pos: srdiag/bpm/c02-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-07 x_pos: srdiag/bpm/c02-07/SA_HPosition y_pos: srdiag/bpm/c02-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-08 x_pos: srdiag/bpm/c02-08/SA_HPosition y_pos: srdiag/bpm/c02-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-09 x_pos: srdiag/bpm/c02-09/SA_HPosition y_pos: srdiag/bpm/c02-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C02-10 x_pos: srdiag/bpm/c02-10/SA_HPosition y_pos: srdiag/bpm/c02-10/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-01 x_pos: srdiag/bpm/c03-01/SA_HPosition y_pos: srdiag/bpm/c03-01/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-02 x_pos: srdiag/bpm/c03-02/SA_HPosition y_pos: srdiag/bpm/c03-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-03 x_pos: srdiag/bpm/c03-03/SA_HPosition y_pos: srdiag/bpm/c03-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-04 x_pos: srdiag/bpm/c03-04/SA_HPosition y_pos: srdiag/bpm/c03-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-05 x_pos: srdiag/bpm/c03-05/SA_HPosition y_pos: srdiag/bpm/c03-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-06 x_pos: srdiag/bpm/c03-06/SA_HPosition y_pos: srdiag/bpm/c03-06/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-07 x_pos: srdiag/bpm/c03-07/SA_HPosition y_pos: srdiag/bpm/c03-07/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-08 x_pos: srdiag/bpm/c03-08/SA_HPosition y_pos: srdiag/bpm/c03-08/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-09 x_pos: srdiag/bpm/c03-09/SA_HPosition y_pos: srdiag/bpm/c03-09/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C03-10 x_pos: srdiag/bpm/c03-10/SA_HPosition y_pos: srdiag/bpm/c03-10/SA_VPosition diff --git a/tests/config/bad_conf_duplicate_2.yaml b/tests/config/bad_conf_duplicate_2.yaml index 346957aa..4430fc2e 100644 --- a/tests/config/bad_conf_duplicate_2.yaml +++ b/tests/config/bad_conf_duplicate_2.yaml @@ -21,15 +21,15 @@ arrays: - BPM_C04-06 - BPM_C04-06 # duplicate name in array devices: -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-04 x_pos: srdiag/bpm/c04-04/SA_HPosition y_pos: srdiag/bpm/c04-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-05 x_pos: srdiag/bpm/c04-05/SA_HPosition y_pos: srdiag/bpm/c04-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-06 x_pos: srdiag/bpm/c04-06/SA_HPosition y_pos: srdiag/bpm/c04-06/SA_VPosition diff --git a/tests/config/bad_conf_duplicate_3.yaml b/tests/config/bad_conf_duplicate_3.yaml index 9149009f..8b5f376a 100644 --- a/tests/config/bad_conf_duplicate_3.yaml +++ b/tests/config/bad_conf_duplicate_3.yaml @@ -22,19 +22,19 @@ arrays: - BPM_C04-05 - BPM_C04-06 devices: -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-04 x_pos: srdiag/bpm/c04-04/SA_HPosition y_pos: srdiag/bpm/c04-04/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-05 x_pos: srdiag/bpm/c04-05/SA_HPosition y_pos: srdiag/bpm/c04-05/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C04-06 x_pos: srdiag/bpm/c04-06/SA_HPosition y_pos: srdiag/bpm/c04-06/SA_VPosition -- type: pyaml.bpm.bpm # duplicate device +- type: pyaml.diagnostics.bpm.bpm # duplicate device name: BPM_C04-06 x_pos: srdiag/bpm/c04-06/SA_HPosition y_pos: srdiag/bpm/c04-06/SA_VPosition diff --git a/tests/config/bpms.yaml b/tests/config/bpms.yaml index 07caf59a..cf2275c1 100644 --- a/tests/config/bpms.yaml +++ b/tests/config/bpms.yaml @@ -13,22 +13,22 @@ controls: catalog: catalogs/sr_catalogs.yaml data_folder: /data/store devices: -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-01 x_pos: srdiag/bpm/c01-01/SA_HPosition y_pos: srdiag/bpm/c01-01/SA_VPosition x_offset: srdiag/bpm/c01-01/HOffset y_offset: srdiag/bpm/c01-01/VOffset tilt: srdiag/bpm/c01-01/Tilt_Angle -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-02 x_pos: srdiag/bpm/c01-02/SA_HPosition y_pos: srdiag/bpm/c01-02/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-03 x_pos: srdiag/bpm/c01-03/SA_HPosition y_pos: srdiag/bpm/c01-03/SA_VPosition -- type: pyaml.bpm.bpm +- type: pyaml.diagnostics.bpm.bpm name: BPM_C01-04 x_pos: srdiag/bpm/c01-04/SA_HPosition y_pos: srdiag/bpm/c01-04/SA_VPosition diff --git a/tests/config/config_manager_sr_devices.yaml b/tests/config/config_manager_sr_devices.yaml index c6d3e212..46ffaad2 100644 --- a/tests/config/config_manager_sr_devices.yaml +++ b/tests/config/config_manager_sr_devices.yaml @@ -1,11 +1,11 @@ devices: - sr/quadrupoles/QF1AC01.yaml - sr/correctors/SH1AC01-C02.yaml - - type: pyaml.bpm.bpm + - type: pyaml.diagnostics.bpm.bpm name: BPM_C04-01 x_pos: srdiag/bpm/c04-01/SA_HPosition y_pos: srdiag/bpm/c04-01/SA_VPosition - - type: pyaml.bpm.bpm + - type: pyaml.diagnostics.bpm.bpm name: BPM_C04-02 x_pos: srdiag/bpm/c04-02/SA_HPosition y_pos: srdiag/bpm/c04-02/SA_VPosition diff --git a/tests/config/sr.yaml b/tests/config/sr.yaml index 3e9561e8..4fbcffd4 100644 --- a/tests/config/sr.yaml +++ b/tests/config/sr.yaml @@ -43,11 +43,11 @@ arrays: devices: - sr/quadrupoles/QF1AC01.yaml - sr/correctors/SH1AC01-C02.yaml - - type: pyaml.bpm.bpm + - type: pyaml.diagnostics.bpm.bpm name: BPM_C04-01 x_pos: srdiag/bpm/c04-01/SA_HPosition y_pos: srdiag/bpm/c04-01/SA_VPosition - - type: pyaml.bpm.bpm + - type: pyaml.diagnostics.bpm.bpm name: BPM_C04-02 x_pos: srdiag/bpm/c04-02/SA_HPosition y_pos: srdiag/bpm/c04-02/SA_VPosition diff --git a/tests/configuration/test_configuration_manager.py b/tests/configuration/test_configuration_manager.py index f36c37dd..4e1c4249 100644 --- a/tests/configuration/test_configuration_manager.py +++ b/tests/configuration/test_configuration_manager.py @@ -189,7 +189,7 @@ def test_configuration_manager_accumulates_multiple_device_fragments( assert manager.has("devices", "BETATRON_TUNE") assert manager.get("devices", "QF1A-C01")["class_path"] == "pyaml.magnet.quadrupole.Quadrupole" assert manager.get("devices", "SH1A-C01")["class_path"] == "pyaml.magnet.cfm_magnet.CombinedFunctionMagnet" - assert manager.get("devices", "BPM_C04-01")["class_path"] == "pyaml.bpm.bpm.BPM" + assert manager.get("devices", "BPM_C04-01")["class_path"] == "pyaml.diagnostics.bpm.bpm.BPM" assert manager.get("devices", "BETATRON_TUNE")["class_path"] == "pyaml.diagnostics.tune_monitor.BetatronTuneMonitor" @@ -273,7 +273,7 @@ def test_configuration_manager_repr_is_yellow_pages_like( assert "live (tango.pyaml.controlsystem.TangoControlSystem) source=config_manager_sr_base.yaml" in output assert "design (pyaml.lattice.simulator.Simulator) source=config_manager_sr_base.yaml" in output assert "HCORR (pyaml.arrays.magnet.Magnet) patterns=1 source=config_manager_sr_arrays.yaml" in output - assert "BPM_C04-01 (pyaml.bpm.bpm.BPM) source=config_manager_sr_devices.yaml" in output + assert "BPM_C04-01 (pyaml.diagnostics.bpm.bpm.BPM) source=config_manager_sr_devices.yaml" in output assert " ." in output diff --git a/tests/bpm/test_bpm.py b/tests/diagnostics/bpm/test_bpm.py similarity index 100% rename from tests/bpm/test_bpm.py rename to tests/diagnostics/bpm/test_bpm.py diff --git a/tests/bpm/test_bpm_controlsystem.py b/tests/diagnostics/bpm/test_bpm_controlsystem.py similarity index 100% rename from tests/bpm/test_bpm_controlsystem.py rename to tests/diagnostics/bpm/test_bpm_controlsystem.py diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index 43627e1d..bbe42f46 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -50,7 +50,7 @@ def test_inline_catalog_is_supported(install_test_package): ], "devices": [ { - "type": "pyaml.bpm.bpm", + "type": "pyaml.diagnostics.bpm.bpm", "name": "BPM_C02-01", "x_pos": "BPM_C02-01/x", "y_pos": "BPM_C02-01/y", @@ -103,7 +103,7 @@ def test_unresolved_catalog_key_raises_runtime_error(install_test_package): ], "devices": [ { - "type": "pyaml.bpm.bpm", + "type": "pyaml.diagnostics.bpm.bpm", "name": "BPM_C03-01", "x_pos": "BPM_C03-01/x", "y_pos": "BPM_C03-01/y", @@ -175,7 +175,7 @@ def test_indexed_catalog_entry_extracts_scalar_from_vector_attribute(install_tes ], "devices": [ { - "type": "pyaml.bpm.bpm", + "type": "pyaml.diagnostics.bpm.bpm", "name": "BPM_TEST", "x_pos": "bpm/SA_HPosition", "y_pos": "bpm/SA_VPosition", From 59bbb471a118b0f5aeb75040e5336df09c427334 Mon Sep 17 00:00:00 2001 From: Teresia Olsson Date: Mon, 5 Oct 2026 13:20:30 +0200 Subject: [PATCH 41/49] Rename file resolver to include. --- pyaml/configuration/fileloader.py | 18 +++++++++--------- tests/configuration/test_fileloader.py | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyaml/configuration/fileloader.py b/pyaml/configuration/fileloader.py index ef5f9f0e..72b615e1 100644 --- a/pyaml/configuration/fileloader.py +++ b/pyaml/configuration/fileloader.py @@ -189,7 +189,7 @@ def resolver(name: str): Args: name: Prefix used to invoke the resolver (for example ``"env"`` - or ``"file"``). + or ``"include"``). Returns: A decorator that registers the decorated function in the global @@ -266,10 +266,10 @@ def resolve_path(value: str, _context: LoadContext | None = None) -> str: return str(ROOT.expand_path(value)) -@resolver("file") -def resolve_file(value: str, context: LoadContext | None = None) -> Any: +@resolver("include") +def resolve_include(value: str, context: LoadContext | None = None) -> Any: """ - Load and return the contents of a configuration file. + Load and include the contents of a configuration file. Parameters ---------- @@ -289,7 +289,7 @@ def resolve_file(value: str, context: LoadContext | None = None) -> Any: If no loading context is provided. """ if context is None: - raise RuntimeError("File resolver requires LoadContext") + raise RuntimeError("Include resolver requires LoadContext") return _load(value, context) @@ -375,7 +375,7 @@ def _expand_string(self, value: str) -> Any: Expand resolver expressions and file references in a string. If the entire string is a resolver expression (for example - ``"${env:HOME}"`` or ``"${file:config.yaml}"``), the resolved value is + ``"${env:HOME}"`` or ``"${include:config.yaml}"``), the resolved value is returned directly and may be of any type. Resolver expressions embedded inside a larger string are interpolated @@ -425,7 +425,7 @@ def replace(match: re.Match[str]) -> str: value = RESOLVER_PATTERN.sub(replace, value) if _is_supported_file(value): - return RESOLVERS["file"](value, self.context) + return RESOLVERS["include"](value, self.context) return value @@ -434,7 +434,7 @@ def _resolve_resolver_expression(self, expr: str) -> Any: Resolve a single resolver expression. The expression must have the form ``":"``, for - example ``"env:HOME"`` or ``"file:config.yaml"``. The resolver is + example ``"env:HOME"`` or ``"include:config.yaml"``. The resolver is looked up in the global resolver registry and invoked with the supplied payload. @@ -504,7 +504,7 @@ def _expand_list(self, items: list) -> list: for item in items: if isinstance(item, str) and _is_supported_file(item): - loaded = RESOLVERS["file"](item, self.context) + loaded = RESOLVERS["include"](item, self.context) if isinstance(loaded, list): expanded.extend(loaded) else: diff --git a/tests/configuration/test_fileloader.py b/tests/configuration/test_fileloader.py index 53b234a6..25b4c44b 100644 --- a/tests/configuration/test_fileloader.py +++ b/tests/configuration/test_fileloader.py @@ -132,12 +132,12 @@ def test_load_nested_json(tmp_path): assert result["child"]["answer"] == 42 -def test_load_file_resolver_loads_nested_file(tmp_path): +def test_load_include_resolver_loads_nested_file(tmp_path): ROOT.set(tmp_path) (tmp_path / "subdir").mkdir() (tmp_path / "subdir" / "child.yaml").write_text("answer: 42\n") - (tmp_path / "parent.yaml").write_text('target: "${file:subdir/child.yaml}"\n') + (tmp_path / "parent.yaml").write_text('target: "${include:subdir/child.yaml}"\n') result = load("parent.yaml") @@ -199,11 +199,11 @@ def test_load_path_resolver_resolves_without_loading_file(tmp_path): assert result["target"] == str((tmp_path / "subdir" / "missing.json").resolve()) -def test_load_interpolated_file_resolver_inside_string_raises(tmp_path): +def test_load_interpolated_include_resolver_inside_string_raises(tmp_path): ROOT.set(tmp_path) (tmp_path / "child.yaml").write_text("answer: 42\n") - (tmp_path / "config.yaml").write_text('value: "prefix-${file:child.yaml}-suffix"\n') + (tmp_path / "config.yaml").write_text('value: "prefix-${include:child.yaml}-suffix"\n') with pytest.raises(PyAMLException, match="cannot be interpolated into a string"): load("config.yaml") From 41790b6454050b0d9e1b18e4df3baebceeac198b Mon Sep 17 00:00:00 2001 From: Teresia Olsson Date: Mon, 5 Oct 2026 17:11:34 +0200 Subject: [PATCH 42/49] Remove support for automatic inclusion of files with specific file extensions and require explicit include resolver. --- pyaml/configuration/fileloader.py | 44 ++++++------------- pyaml/configuration/restfetcher.py | 41 ++++++----------- tests/common/test_errors.py | 10 ++--- tests/config/EBSNames-linker.yaml | 2 +- tests/config/EBSNames.yaml | 2 +- tests/config/EBSOrbit.yaml | 2 +- tests/config/EBSTune-patterns.yaml | 2 +- tests/config/EBSTune.yaml | 2 +- tests/config/EBS_rf_multi.yaml | 2 +- tests/config/EBS_rf_notrans.yaml | 2 +- tests/config/bad_conf.yml | 6 +-- tests/config/bad_conf_cycles.json | 8 ++-- tests/config/bad_conf_cycles.yml | 8 ++-- tests/config/bad_conf_duplicate_1.yaml | 6 +-- tests/config/bad_conf_duplicate_2.yaml | 2 +- tests/config/bad_conf_duplicate_3.yaml | 2 +- tests/config/bad_conf_duplicate_4.yaml | 6 +-- tests/config/bpms.yaml | 4 +- tests/config/catalogs/ebs_catalogs.yaml | 12 ++--- tests/config/config_manager_sr_base.yaml | 2 +- tests/config/config_manager_sr_catalogs.yaml | 2 +- tests/config/config_manager_sr_devices.yaml | 4 +- tests/config/sr-attribute-linker.yaml | 6 +-- tests/config/sr-ident-cfm.yaml | 8 ++-- tests/config/sr-range-cfm.yaml | 8 ++-- tests/config/sr.yaml | 6 +-- tests/config/sr/correctors/SH1AC01-C02.yaml | 4 +- tests/config/sr/correctors/SH1AC01-ident.yaml | 2 +- tests/config/sr/correctors/SH1AC01-range.yaml | 2 +- tests/config/sr/correctors/SH1AC01.yaml | 2 +- tests/config/sr/correctors/SH1AC02.yaml | 2 +- tests/config/sr/magnet_models/QF1AC01.json | 2 +- tests/config/sr/magnet_models/QF1AC01.yaml | 2 +- .../sr/quadrupoles/QD5D-C04_serialized.yaml | 2 +- tests/config/sr/quadrupoles/QF1AC01.json | 2 +- tests/config/sr/quadrupoles/QF1AC01.yaml | 2 +- tests/config/sr/quadrupoles/QF1C01A_2.yaml | 2 +- tests/config/tune_monitor.yaml | 2 +- .../test_configuration_manager.py | 2 +- tests/configuration/test_fileloader.py | 21 +++------ tests/test_accelerator_load.py | 2 +- 41 files changed, 105 insertions(+), 145 deletions(-) diff --git a/pyaml/configuration/fileloader.py b/pyaml/configuration/fileloader.py index 72b615e1..c8b09aa9 100644 --- a/pyaml/configuration/fileloader.py +++ b/pyaml/configuration/fileloader.py @@ -29,7 +29,6 @@ LOCATION_KEY = "__location__" FIELD_LOCATIONS_KEY = "__fieldlocations__" -ACCEPTED_SUFFIXES = (".yaml", ".yml", ".json") RESOLVER_PATTERN = re.compile(r"\$\{([^{}]+)\}") @@ -323,11 +322,6 @@ def _load(filename: str, context: LoadContext) -> Union[dict, list]: return loader.load() -def _is_supported_file(value: Any) -> bool: - """Return True if the value looks like a supported configuration file name.""" - return isinstance(value, str) and value.endswith(ACCEPTED_SUFFIXES) - - class ConfigLoader(ABC): """ Base class for parsers that expand nested configuration references. @@ -424,9 +418,6 @@ def replace(match: re.Match[str]) -> str: value = RESOLVER_PATTERN.sub(replace, value) - if _is_supported_file(value): - return RESOLVERS["include"](value, self.context) - return value def _resolve_resolver_expression(self, expr: str) -> Any: @@ -487,31 +478,24 @@ def _expand_list(self, items: list) -> list: """ Recursively expand the elements of a list. - Plain string values that refer to supported configuration files are - treated as list includes. If the referenced file loads to a list, its - elements are spliced into the current list. Otherwise, the loaded - object is appended as a single element. - - All other items are expanded recursively using :meth:`expand`. - - Args: - items: The list to expand. - - Returns: - The expanded list. + Lists returned by an explicit include resolver are spliced into the + current list. Other nested lists are preserved. """ expanded: list[Any] = [] for item in items: - if isinstance(item, str) and _is_supported_file(item): - loaded = RESOLVERS["include"](item, self.context) - if isinstance(loaded, list): - expanded.extend(loaded) - else: - expanded.append(loaded) - continue - - expanded.append(self.expand(item)) + is_include = ( + isinstance(item, str) + and (match := RESOLVER_PATTERN.fullmatch(item)) is not None + and match.group(1).strip().startswith("include:") + ) + + value = self.expand(item) + + if is_include and isinstance(value, list): + expanded.extend(value) + else: + expanded.append(value) return expanded diff --git a/pyaml/configuration/restfetcher.py b/pyaml/configuration/restfetcher.py index 96a75450..0ae0612f 100644 --- a/pyaml/configuration/restfetcher.py +++ b/pyaml/configuration/restfetcher.py @@ -18,12 +18,12 @@ from yaml import CLoader from ..common.exception import PyAMLConfigException -from .fileloader import ACCEPTED_SUFFIXES, SafeLineLoader +from .fileloader import SafeLineLoader REMOTE_BASE_URL_KEY = "__baseurl__" SourceRoot = Path | str | None _REMOTE_SCHEMES = {"http", "https"} - +INCLUDE_PREFIX = "${include:" FILE_PREFIX = "${path:" @@ -285,9 +285,10 @@ def _expand_remote_dict( """ values.setdefault(REMOTE_BASE_URL_KEY, base_url) for key, value in list(values.items()): - if _is_config_reference(value): + if _is_include(value): + reference = _include_reference(value) values[key] = _load_remote_document( - _resolve_remote_config_reference(value, base_url), + _resolve_remote_config_reference(reference, base_url), include_locations=include_locations, stack=stack, ) @@ -324,9 +325,10 @@ def _expand_remote_list(values: list[Any], base_url: str, stack: list[str], *, i index = 0 while index < len(values): value = values[index] - if _is_config_reference(value): + if _is_include(value): + reference = _include_reference(value) expanded = _load_remote_document( - _resolve_remote_config_reference(value, base_url), + _resolve_remote_config_reference(reference, base_url), include_locations=include_locations, stack=stack, ) @@ -348,29 +350,14 @@ def _expand_remote_list(values: list[Any], base_url: str, stack: list[str], *, i return values -def _is_config_reference(value: Any) -> bool: - """ - Return whether a value names a supported configuration document. - - Parameters - ---------- - value : Any - Value to inspect. - - Returns - ------- - bool - ``True`` for a YAML, JSON, or supported remote document reference. - """ - if not isinstance(value, str): - return False +def _is_include(value: Any) -> bool: + """Return whether a value is an explicit include expression.""" + return isinstance(value, str) and value.startswith(INCLUDE_PREFIX) and value.endswith("}") - if value.startswith(FILE_PREFIX): - return False - parsed = urlparse(value) - path = parsed.path if parsed.scheme in _REMOTE_SCHEMES else value - return any(path.endswith(suffix) for suffix in ACCEPTED_SUFFIXES) +def _include_reference(value: str) -> str: + """Return the reference contained in an include expression.""" + return value[len(INCLUDE_PREFIX) : -1].strip() def _resolve_remote_config_reference(reference: str, base_url: str) -> str: diff --git a/tests/common/test_errors.py b/tests/common/test_errors.py index 38ce79f5..37814410 100644 --- a/tests/common/test_errors.py +++ b/tests/common/test_errors.py @@ -60,7 +60,7 @@ def test_duplicate_error_reports_source_line_and_column_across_files(tmp_path): ) root = tmp_path / "root.yaml" root.write_text( - "type: pyaml.accelerator\ndevices:\n - devices_a.yaml\n - devices_b.yaml\n", + "type: pyaml.accelerator\ndevices:\n - ${include:devices_a.yaml}\n - ${include:devices_b.yaml}\n", encoding="utf-8", ) @@ -86,7 +86,7 @@ def test_malformed_local_included_yaml_reports_source_line_and_column(tmp_path): "machine: sr\n" "energy: 6e9\n" "data_folder: /data/store\n" - "devices: broken_devices.yaml\n", + "devices: ${include:broken_devices.yaml}\n", encoding="utf-8", ) @@ -111,7 +111,7 @@ def test_truncated_local_included_json_reports_source_and_position(tmp_path): "machine: sr\n" "energy: 6e9\n" "data_folder: /data/store\n" - "devices: broken_devices.json\n", + "devices: ${include:broken_devices.json}\n", encoding="utf-8", ) @@ -131,7 +131,7 @@ def test_malformed_remote_included_yaml_reports_source_line_and_column(http_conf "machine: sr\n" "energy: 6000000000.0\n" "data_folder: /data/store\n" - "devices: fragments/broken_devices.yaml\n" + "devices: ${include:fragments/broken_devices.yaml}\n" ), "/configs/fragments/broken_devices.yaml": ("- type: pyaml.bpm.bpm\n name: BPM_BROKEN\n model:\n type: [oops\n"), } @@ -153,7 +153,7 @@ def test_truncated_remote_included_json_reports_source_and_position(http_config_ "machine: sr\n" "energy: 6000000000.0\n" "data_folder: /data/store\n" - "devices: fragments/broken_devices.json\n" + "devices: ${include:fragments/broken_devices.json}\n" ), "/configs/fragments/broken_devices.json": ( '{"type": "pyaml.bpm.bpm", "name": "BPM_BROKEN", "model": ', diff --git a/tests/config/EBSNames-linker.yaml b/tests/config/EBSNames-linker.yaml index 98ae50b2..e322320e 100644 --- a/tests/config/EBSNames-linker.yaml +++ b/tests/config/EBSNames-linker.yaml @@ -13,7 +13,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/ebs_catalogs.yaml + catalog: ${include:catalogs/ebs_catalogs.yaml} data_folder: /data/store devices: - type: pyaml.magnet.quadrupole diff --git a/tests/config/EBSNames.yaml b/tests/config/EBSNames.yaml index b0eee6af..cde71065 100644 --- a/tests/config/EBSNames.yaml +++ b/tests/config/EBSNames.yaml @@ -10,7 +10,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/ebs_catalogs.yaml + catalog: ${include:catalogs/ebs_catalogs.yaml} data_folder: /data/store devices: - type: pyaml.magnet.quadrupole diff --git a/tests/config/EBSOrbit.yaml b/tests/config/EBSOrbit.yaml index 5d6ea257..3a6516cf 100644 --- a/tests/config/EBSOrbit.yaml +++ b/tests/config/EBSOrbit.yaml @@ -11,7 +11,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/ebs_catalogs.yaml + catalog: ${include:catalogs/ebs_catalogs.yaml} data_folder: /data/store arrays: - type: pyaml.arrays.magnet diff --git a/tests/config/EBSTune-patterns.yaml b/tests/config/EBSTune-patterns.yaml index 0c258bde..c0bad0f4 100644 --- a/tests/config/EBSTune-patterns.yaml +++ b/tests/config/EBSTune-patterns.yaml @@ -12,7 +12,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/ebs_catalogs.yaml + catalog: ${include:catalogs/ebs_catalogs.yaml} data_folder: /data/store arrays: - type: pyaml.arrays.magnet diff --git a/tests/config/EBSTune.yaml b/tests/config/EBSTune.yaml index f5a8d5c0..52c7d011 100644 --- a/tests/config/EBSTune.yaml +++ b/tests/config/EBSTune.yaml @@ -12,7 +12,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/ebs_catalogs.yaml + catalog: ${include:catalogs/ebs_catalogs.yaml} data_folder: /data/store arrays: - type: pyaml.arrays.magnet diff --git a/tests/config/EBS_rf_multi.yaml b/tests/config/EBS_rf_multi.yaml index d3c7133b..f1025118 100644 --- a/tests/config/EBS_rf_multi.yaml +++ b/tests/config/EBS_rf_multi.yaml @@ -10,7 +10,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/ebs_catalogs.yaml + catalog: ${include:catalogs/ebs_catalogs.yaml} data_folder: /data/store devices: - type: pyaml.rf.rf_plant diff --git a/tests/config/EBS_rf_notrans.yaml b/tests/config/EBS_rf_notrans.yaml index 1306d8c2..daff3279 100644 --- a/tests/config/EBS_rf_notrans.yaml +++ b/tests/config/EBS_rf_notrans.yaml @@ -10,7 +10,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/ebs_catalogs.yaml + catalog: ${include:catalogs/ebs_catalogs.yaml} data_folder: /data/store devices: - type: pyaml.rf.rf_plant diff --git a/tests/config/bad_conf.yml b/tests/config/bad_conf.yml index 178733b4..513aafeb 100644 --- a/tests/config/bad_conf.yml +++ b/tests/config/bad_conf.yml @@ -19,6 +19,6 @@ arrays: - SH1A-C01-V - SH1A-C02-V devices: - - sr/quadrupoles/QF1AC01.yaml - - sr/correctors/SH1AC01.yaml - - sr/correctors/SH1AC02.yaml + - ${include:sr/quadrupoles/QF1AC01.yaml} + - ${include:sr/correctors/SH1AC01.yaml} + - ${include:sr/correctors/SH1AC02.yaml} diff --git a/tests/config/bad_conf_cycles.json b/tests/config/bad_conf_cycles.json index 26af53c1..11f0d649 100644 --- a/tests/config/bad_conf_cycles.json +++ b/tests/config/bad_conf_cycles.json @@ -3,7 +3,7 @@ "facility": "ESRF", "machine": "sr", "energy": 6e9, - "simulators": ["../config/bad_conf_cycles.json"], + "simulators": ["${include:../config/bad_conf_cycles.json}"], "data_folder": "/data/store", "arrays": [ { @@ -21,8 +21,8 @@ } ], "devices": [ - "sr/quadrupoles/QF1AC01.yaml", - "sr/correctors/SH1AC01.yaml", - "sr/correctors/SH1AC02.yaml" + "${include:sr/quadrupoles/QF1AC01.yaml}", + "${include:sr/correctors/SH1AC01.yaml}", + "${include:sr/correctors/SH1AC02.yaml}" ] } diff --git a/tests/config/bad_conf_cycles.yml b/tests/config/bad_conf_cycles.yml index c0361864..7cfa167d 100644 --- a/tests/config/bad_conf_cycles.yml +++ b/tests/config/bad_conf_cycles.yml @@ -19,7 +19,7 @@ arrays: - SH1A-C01-V - SH1A-C02-V devices: - - ../config/bad_conf_cycles.yml # Cycle here - - sr/quadrupoles/QF1AC01.yaml - - sr/correctors/SH1AC01.yaml - - sr/correctors/SH1AC02.yaml + - ${include:../config/bad_conf_cycles.yml} # Cycle here + - ${include:sr/quadrupoles/QF1AC01.yaml} + - ${include:sr/correctors/SH1AC01.yaml} + - ${include:sr/correctors/SH1AC02.yaml} diff --git a/tests/config/bad_conf_duplicate_1.yaml b/tests/config/bad_conf_duplicate_1.yaml index 209c3be0..608d0765 100644 --- a/tests/config/bad_conf_duplicate_1.yaml +++ b/tests/config/bad_conf_duplicate_1.yaml @@ -20,6 +20,6 @@ arrays: - SH1A-C01-V - SH1A-C02-V devices: - - sr/quadrupoles/QF1AC01.yaml - - sr/correctors/SH1AC01.yaml - - sr/correctors/SH1AC02.yaml + - ${include:sr/quadrupoles/QF1AC01.yaml} + - ${include:sr/correctors/SH1AC01.yaml} + - ${include:sr/correctors/SH1AC02.yaml} diff --git a/tests/config/bad_conf_duplicate_2.yaml b/tests/config/bad_conf_duplicate_2.yaml index 346957aa..830de340 100644 --- a/tests/config/bad_conf_duplicate_2.yaml +++ b/tests/config/bad_conf_duplicate_2.yaml @@ -10,7 +10,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/sr_catalogs.yaml + catalog: ${include:catalogs/sr_catalogs.yaml} data_folder: /data/store arrays: - type: pyaml.arrays.bpm diff --git a/tests/config/bad_conf_duplicate_3.yaml b/tests/config/bad_conf_duplicate_3.yaml index 9149009f..872ab122 100644 --- a/tests/config/bad_conf_duplicate_3.yaml +++ b/tests/config/bad_conf_duplicate_3.yaml @@ -12,7 +12,7 @@ controls: name: live catalog: bpm-catalog catalogs: - - catalogs/sr_catalogs.yaml + - ${include:catalogs/sr_catalogs.yaml} data_folder: /data/store arrays: - type: pyaml.arrays.bpm diff --git a/tests/config/bad_conf_duplicate_4.yaml b/tests/config/bad_conf_duplicate_4.yaml index 95beb2f2..fc1faad0 100644 --- a/tests/config/bad_conf_duplicate_4.yaml +++ b/tests/config/bad_conf_duplicate_4.yaml @@ -19,6 +19,6 @@ arrays: - SH1A-C01-V - SH1A-C02-V devices: - - sr/quadrupoles/QF1AC01.yaml - - sr/correctors/SH1AC01.yaml - - sr/correctors/SH1AC02.yaml + - ${include:sr/quadrupoles/QF1AC01.yaml} + - ${include:sr/correctors/SH1AC01.yaml} + - ${include:sr/correctors/SH1AC02.yaml} diff --git a/tests/config/bpms.yaml b/tests/config/bpms.yaml index 07caf59a..34347483 100644 --- a/tests/config/bpms.yaml +++ b/tests/config/bpms.yaml @@ -10,7 +10,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/sr_catalogs.yaml + catalog: ${include:catalogs/sr_catalogs.yaml} data_folder: /data/store devices: - type: pyaml.bpm.bpm @@ -42,4 +42,4 @@ devices: - [B0, SH1A-C01-H] - [A0, SH1A-C01-V] - [A1, SH1A-C01-SQ] - model: sr/magnet_models/SH1AC01.yaml + model: ${include:sr/magnet_models/SH1AC01.yaml} diff --git a/tests/config/catalogs/ebs_catalogs.yaml b/tests/config/catalogs/ebs_catalogs.yaml index d15349d6..b3da9a0d 100644 --- a/tests/config/catalogs/ebs_catalogs.yaml +++ b/tests/config/catalogs/ebs_catalogs.yaml @@ -1,8 +1,8 @@ type: tango.pyaml.static_catalog entries: - - catalogs/ebs_bpm_catalogs.yaml - - catalogs/ebs_rf_catalogs.yaml - - catalogs/ebs_diag_catalogs.yaml - - catalogs/ebs_magnets_catalogs.yaml - - catalogs/ebs_sh_catalogs.yaml - - catalogs/ebs_sext_catalogs.yaml + - ${include:catalogs/ebs_bpm_catalogs.yaml} + - ${include:catalogs/ebs_rf_catalogs.yaml} + - ${include:catalogs/ebs_diag_catalogs.yaml} + - ${include:catalogs/ebs_magnets_catalogs.yaml} + - ${include:catalogs/ebs_sh_catalogs.yaml} + - ${include:catalogs/ebs_sext_catalogs.yaml} diff --git a/tests/config/config_manager_sr_base.yaml b/tests/config/config_manager_sr_base.yaml index c842221a..0d829bfd 100644 --- a/tests/config/config_manager_sr_base.yaml +++ b/tests/config/config_manager_sr_base.yaml @@ -10,5 +10,5 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/sr_catalogs.yaml + catalog: ${include:catalogs/sr_catalogs.yaml} data_folder: /data/store diff --git a/tests/config/config_manager_sr_catalogs.yaml b/tests/config/config_manager_sr_catalogs.yaml index 2a0f9334..6fae41e5 100644 --- a/tests/config/config_manager_sr_catalogs.yaml +++ b/tests/config/config_manager_sr_catalogs.yaml @@ -1,2 +1,2 @@ catalogs: - - catalogs/sr_catalogs.yaml + - ${include:catalogs/sr_catalogs.yaml} diff --git a/tests/config/config_manager_sr_devices.yaml b/tests/config/config_manager_sr_devices.yaml index c6d3e212..20b8fd9a 100644 --- a/tests/config/config_manager_sr_devices.yaml +++ b/tests/config/config_manager_sr_devices.yaml @@ -1,6 +1,6 @@ devices: - - sr/quadrupoles/QF1AC01.yaml - - sr/correctors/SH1AC01-C02.yaml + - ${include:sr/quadrupoles/QF1AC01.yaml} + - ${include:sr/correctors/SH1AC01-C02.yaml} - type: pyaml.bpm.bpm name: BPM_C04-01 x_pos: srdiag/bpm/c04-01/SA_HPosition diff --git a/tests/config/sr-attribute-linker.yaml b/tests/config/sr-attribute-linker.yaml index 1eb8edbb..98d531d8 100644 --- a/tests/config/sr-attribute-linker.yaml +++ b/tests/config/sr-attribute-linker.yaml @@ -22,6 +22,6 @@ arrays: - SH1A-C01-V - SH1A-C02-V devices: - - sr/quadrupoles/QF1AC01.yaml - - sr/correctors/SH1AC01.yaml - - sr/correctors/SH1AC02.yaml + - ${include:sr/quadrupoles/QF1AC01.yaml} + - ${include:sr/correctors/SH1AC01.yaml} + - ${include:sr/correctors/SH1AC02.yaml} diff --git a/tests/config/sr-ident-cfm.yaml b/tests/config/sr-ident-cfm.yaml index 7bc833fd..11e0fbd4 100644 --- a/tests/config/sr-ident-cfm.yaml +++ b/tests/config/sr-ident-cfm.yaml @@ -10,7 +10,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/sr_catalogs.yaml + catalog: ${include:catalogs/sr_catalogs.yaml} data_folder: /data/store arrays: - type: pyaml.arrays.magnet @@ -24,6 +24,6 @@ arrays: - SH1A-C01-V - SH1A-C02-V devices: - - sr/quadrupoles/QF1AC01.yaml - - sr/correctors/SH1AC01-ident.yaml - - sr/correctors/SH1AC02.yaml + - ${include:sr/quadrupoles/QF1AC01.yaml} + - ${include:sr/correctors/SH1AC01-ident.yaml} + - ${include:sr/correctors/SH1AC02.yaml} diff --git a/tests/config/sr-range-cfm.yaml b/tests/config/sr-range-cfm.yaml index fc1b3721..466531e7 100644 --- a/tests/config/sr-range-cfm.yaml +++ b/tests/config/sr-range-cfm.yaml @@ -10,7 +10,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/sr_catalogs.yaml + catalog: ${include:catalogs/sr_catalogs.yaml} data_folder: /data/store arrays: - type: pyaml.arrays.magnet @@ -24,6 +24,6 @@ arrays: - SH1A-C01-V - SH1A-C02-V devices: - - sr/quadrupoles/QF1AC01.yaml - - sr/correctors/SH1AC01-range.yaml - - sr/correctors/SH1AC02.yaml + - ${include:sr/quadrupoles/QF1AC01.yaml} + - ${include:sr/correctors/SH1AC01-range.yaml} + - ${include:sr/correctors/SH1AC02.yaml} diff --git a/tests/config/sr.yaml b/tests/config/sr.yaml index 3e9561e8..05dc9699 100644 --- a/tests/config/sr.yaml +++ b/tests/config/sr.yaml @@ -10,7 +10,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/sr_catalogs.yaml + catalog: ${include:catalogs/sr_catalogs.yaml} data_folder: /data/store arrays: - type: pyaml.arrays.magnet @@ -41,8 +41,8 @@ arrays: - SH1A-C01-V - SH1A-C02-H devices: - - sr/quadrupoles/QF1AC01.yaml - - sr/correctors/SH1AC01-C02.yaml + - ${include:sr/quadrupoles/QF1AC01.yaml} + - ${include:sr/correctors/SH1AC01-C02.yaml} - type: pyaml.bpm.bpm name: BPM_C04-01 x_pos: srdiag/bpm/c04-01/SA_HPosition diff --git a/tests/config/sr/correctors/SH1AC01-C02.yaml b/tests/config/sr/correctors/SH1AC01-C02.yaml index 0bba2e4f..295d1e52 100644 --- a/tests/config/sr/correctors/SH1AC01-C02.yaml +++ b/tests/config/sr/correctors/SH1AC01-C02.yaml @@ -8,7 +8,7 @@ - [B0, SH1A-C01-H] - [A0, SH1A-C01-V] - [A1, SH1A-C01-SQ] - model: sr/magnet_models/SH1AC01.yaml + model: ${include:sr/magnet_models/SH1AC01.yaml} - type: pyaml.magnet.cfm_magnet name: SH1A-C02 #Name of the element in the lattice model mapping: @@ -19,4 +19,4 @@ - [B0, SH1A-C02-H] - [A0, SH1A-C02-V] - [A1, SH1A-C02-SQ] - model: sr/magnet_models/SH1AC02.yaml + model: ${include:sr/magnet_models/SH1AC02.yaml} diff --git a/tests/config/sr/correctors/SH1AC01-ident.yaml b/tests/config/sr/correctors/SH1AC01-ident.yaml index f5f56895..0f634c2a 100644 --- a/tests/config/sr/correctors/SH1AC01-ident.yaml +++ b/tests/config/sr/correctors/SH1AC01-ident.yaml @@ -8,4 +8,4 @@ mapping: - [B0, SH1A-C01-H] - [A0, SH1A-C01-V] - [A1, SH1A-C01-SQ] -model: sr/magnet_models/SH1AC01-ident.yaml +model: ${include:sr/magnet_models/SH1AC01-ident.yaml} diff --git a/tests/config/sr/correctors/SH1AC01-range.yaml b/tests/config/sr/correctors/SH1AC01-range.yaml index ff9eeee8..0cffc86c 100644 --- a/tests/config/sr/correctors/SH1AC01-range.yaml +++ b/tests/config/sr/correctors/SH1AC01-range.yaml @@ -8,4 +8,4 @@ mapping: - [B0, SH1A-C01-H] - [A0, SH1A-C01-V] - [A1, SH1A-C01-SQ] -model: sr/magnet_models/SH1AC01-range.yaml +model: ${include:sr/magnet_models/SH1AC01-range.yaml} diff --git a/tests/config/sr/correctors/SH1AC01.yaml b/tests/config/sr/correctors/SH1AC01.yaml index 717ad973..9facc0be 100644 --- a/tests/config/sr/correctors/SH1AC01.yaml +++ b/tests/config/sr/correctors/SH1AC01.yaml @@ -8,4 +8,4 @@ mapping: - [B0, SH1A-C01-H] - [A0, SH1A-C01-V] - [A1, SH1A-C01-SQ] -model: sr/magnet_models/SH1AC01.yaml +model: ${include:sr/magnet_models/SH1AC01.yaml} diff --git a/tests/config/sr/correctors/SH1AC02.yaml b/tests/config/sr/correctors/SH1AC02.yaml index 590d69b9..3818d5ad 100644 --- a/tests/config/sr/correctors/SH1AC02.yaml +++ b/tests/config/sr/correctors/SH1AC02.yaml @@ -8,4 +8,4 @@ mapping: - [B0, SH1A-C02-H] - [A0, SH1A-C02-V] - [A1, SH1A-C02-SQ] -model: sr/magnet_models/SH1AC02.yaml +model: ${include:sr/magnet_models/SH1AC02.yaml} diff --git a/tests/config/sr/magnet_models/QF1AC01.json b/tests/config/sr/magnet_models/QF1AC01.json index b9b4e8ab..966a5a45 100644 --- a/tests/config/sr/magnet_models/QF1AC01.json +++ b/tests/config/sr/magnet_models/QF1AC01.json @@ -2,7 +2,7 @@ "type": "pyaml.magnet.linear_model", "calibration_factor": 1.00504, "calibration_offset": 0.0, - "curve": "sr/magnet_models/quadcurve.json", + "curve": "${include:sr/magnet_models/quadcurve.json}", "unit": "1/m", "hardware_unit": "A", "powerconverter": "sr/ps-qf1/c01-a/current" diff --git a/tests/config/sr/magnet_models/QF1AC01.yaml b/tests/config/sr/magnet_models/QF1AC01.yaml index c2f1ac30..8d31dee1 100644 --- a/tests/config/sr/magnet_models/QF1AC01.yaml +++ b/tests/config/sr/magnet_models/QF1AC01.yaml @@ -1,7 +1,7 @@ type: pyaml.magnet.linear_model calibration_factor: 1.00504 calibration_offset: 0.0 -curve: sr/magnet_models/quadcurve.yaml +curve: ${include:sr/magnet_models/quadcurve.yaml} unit: 1/m hardware_unit: A powerconverter: sr/ps-qf1/c01-a/current diff --git a/tests/config/sr/quadrupoles/QD5D-C04_serialized.yaml b/tests/config/sr/quadrupoles/QD5D-C04_serialized.yaml index 18f5a912..48dcc7e2 100644 --- a/tests/config/sr/quadrupoles/QD5D-C04_serialized.yaml +++ b/tests/config/sr/quadrupoles/QD5D-C04_serialized.yaml @@ -1,6 +1,6 @@ type: pyaml.magnet.quadrupole name: QD5D-C04 -model: sr/magnet_models/QF1AC01.yaml +model: ${include:sr/magnet_models/QF1AC01.yaml} linked_elements: - QF8B-C04 - QF8D-C04 diff --git a/tests/config/sr/quadrupoles/QF1AC01.json b/tests/config/sr/quadrupoles/QF1AC01.json index 655d0699..164fdccd 100644 --- a/tests/config/sr/quadrupoles/QF1AC01.json +++ b/tests/config/sr/quadrupoles/QF1AC01.json @@ -1,5 +1,5 @@ { "type": "pyaml.magnet.quadrupole", "name": "QF1A-C01", - "model": "sr/magnet_models/QF1AC01.json" + "model": "${include:sr/magnet_models/QF1AC01.json}" } diff --git a/tests/config/sr/quadrupoles/QF1AC01.yaml b/tests/config/sr/quadrupoles/QF1AC01.yaml index 627702ad..507d3d9d 100644 --- a/tests/config/sr/quadrupoles/QF1AC01.yaml +++ b/tests/config/sr/quadrupoles/QF1AC01.yaml @@ -1,3 +1,3 @@ type: pyaml.magnet.quadrupole name: QF1A-C01 -model: sr/magnet_models/QF1AC01.yaml +model: ${include:sr/magnet_models/QF1AC01.yaml} diff --git a/tests/config/sr/quadrupoles/QF1C01A_2.yaml b/tests/config/sr/quadrupoles/QF1C01A_2.yaml index 6ab7f120..6c9c21e7 100644 --- a/tests/config/sr/quadrupoles/QF1C01A_2.yaml +++ b/tests/config/sr/quadrupoles/QF1C01A_2.yaml @@ -4,7 +4,7 @@ model: type: pyaml.magnet.linear_model calibration_factor: 1.006560 calibration_offset: 0.0034 - curve: sr/magnet_models/quadcurve.yaml + curve: ${include:sr/magnet_models/quadcurve.yaml} unit: 1/m hardware_unit: A powerconverter: sr/ps-qf1/c01-a/current diff --git a/tests/config/tune_monitor.yaml b/tests/config/tune_monitor.yaml index f782a2c1..791a62de 100644 --- a/tests/config/tune_monitor.yaml +++ b/tests/config/tune_monitor.yaml @@ -11,7 +11,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/ebs_catalogs.yaml + catalog: ${include:catalogs/ebs_catalogs.yaml} data_folder: /data/store devices: - type: pyaml.diagnostics.tune_monitor diff --git a/tests/configuration/test_configuration_manager.py b/tests/configuration/test_configuration_manager.py index f36c37dd..a8c98ec2 100644 --- a/tests/configuration/test_configuration_manager.py +++ b/tests/configuration/test_configuration_manager.py @@ -222,7 +222,7 @@ def test_configuration_manager_adds_remote_source_with_relative_includes(http_co energy: 3000000000.0 data_folder: remote-data description: Loaded over HTTP -simulators: fragments/simulators.json +simulators: ${include:fragments/simulators.json} devices: [] """, "/configs/fragments/simulators.json": """ diff --git a/tests/configuration/test_fileloader.py b/tests/configuration/test_fileloader.py index 25b4c44b..6fae602a 100644 --- a/tests/configuration/test_fileloader.py +++ b/tests/configuration/test_fileloader.py @@ -9,7 +9,6 @@ ROOT, LoadContext, RootFolder, - _is_supported_file, load, ) @@ -54,16 +53,6 @@ def test_rootfolder_set_updates_root(tmp_path): assert after == (new_root / "config.yaml").resolve() -def test_is_supported_file(): - assert _is_supported_file("config.yaml") - assert _is_supported_file("config.yml") - assert _is_supported_file("config.json") - - assert not _is_supported_file("config.txt") - assert not _is_supported_file(1) - assert not _is_supported_file(None) - - def test_load_context_adds_and_removes_paths(tmp_path): ctx = LoadContext() path = tmp_path / "config.yaml" @@ -113,7 +102,7 @@ def test_load_nested_yaml(tmp_path): (tmp_path / "child.yaml").write_text("answer: 42\n") - (tmp_path / "parent.yaml").write_text("child: child.yaml\n") + (tmp_path / "parent.yaml").write_text("child: ${include:child.yaml}\n") result = load("parent.yaml") @@ -125,7 +114,7 @@ def test_load_nested_json(tmp_path): (tmp_path / "child.json").write_text(json.dumps({"answer": 42})) - (tmp_path / "parent.json").write_text(json.dumps({"child": "child.json"})) + (tmp_path / "parent.json").write_text(json.dumps({"child": "${include:child.json}"})) result = load("parent.json") @@ -227,7 +216,7 @@ def test_load_list_include_extends_list(tmp_path): """ values: - start - - items.yaml + - ${include:items.yaml} - end """.strip() ) @@ -285,8 +274,8 @@ def test_load_invalid_json_raises_pyaml_exception(tmp_path): def test_load_circular_include_raises_pyaml_exception(tmp_path): ROOT.set(tmp_path) - (tmp_path / "a.yaml").write_text("b: b.yaml\n") - (tmp_path / "b.yaml").write_text("a: a.yaml\n") + (tmp_path / "a.yaml").write_text("b: ${include:b.yaml}\n") + (tmp_path / "b.yaml").write_text("a: ${include:a.yaml}\n") with pytest.raises(PyAMLException, match="Circular file inclusion"): load("a.yaml") diff --git a/tests/test_accelerator_load.py b/tests/test_accelerator_load.py index f30a68e3..caedcb24 100644 --- a/tests/test_accelerator_load.py +++ b/tests/test_accelerator_load.py @@ -73,7 +73,7 @@ def test_accelerator_load_supports_remote_sources(http_config_server, ebs_lattic energy: 3000000000.0 data_folder: remote-data description: Remote accelerator -simulators: fragments/simulators.json +simulators: ${include:fragments/simulators.json} devices: [] """, "/config/fragments/simulators.json": f""" From 836eb545701f1325acc88e1a3ce0f97555451880 Mon Sep 17 00:00:00 2001 From: Teresia Olsson Date: Mon, 5 Oct 2026 17:29:00 +0200 Subject: [PATCH 43/49] Update example configurations to new syntax. --- .../use_cases/config/esrf/catalogs/ebs_catalogs.yaml | 12 ++++++------ examples/use_cases/config/esrf/esrf.yaml | 2 +- examples/use_cases/config/soleil_ii/p.yaml | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/use_cases/config/esrf/catalogs/ebs_catalogs.yaml b/examples/use_cases/config/esrf/catalogs/ebs_catalogs.yaml index d15349d6..b3da9a0d 100644 --- a/examples/use_cases/config/esrf/catalogs/ebs_catalogs.yaml +++ b/examples/use_cases/config/esrf/catalogs/ebs_catalogs.yaml @@ -1,8 +1,8 @@ type: tango.pyaml.static_catalog entries: - - catalogs/ebs_bpm_catalogs.yaml - - catalogs/ebs_rf_catalogs.yaml - - catalogs/ebs_diag_catalogs.yaml - - catalogs/ebs_magnets_catalogs.yaml - - catalogs/ebs_sh_catalogs.yaml - - catalogs/ebs_sext_catalogs.yaml + - ${include:catalogs/ebs_bpm_catalogs.yaml} + - ${include:catalogs/ebs_rf_catalogs.yaml} + - ${include:catalogs/ebs_diag_catalogs.yaml} + - ${include:catalogs/ebs_magnets_catalogs.yaml} + - ${include:catalogs/ebs_sh_catalogs.yaml} + - ${include:catalogs/ebs_sext_catalogs.yaml} diff --git a/examples/use_cases/config/esrf/esrf.yaml b/examples/use_cases/config/esrf/esrf.yaml index a3bdba75..57b952ea 100644 --- a/examples/use_cases/config/esrf/esrf.yaml +++ b/examples/use_cases/config/esrf/esrf.yaml @@ -11,7 +11,7 @@ controls: - type: tango.pyaml.controlsystem tango_host: ebs-simu-3:10000 name: live - catalog: catalogs/ebs_catalogs.yaml + catalog: ${include:catalogs/ebs_catalogs.yaml} data_folder: /data/store arrays: - type: pyaml.arrays.magnet diff --git a/examples/use_cases/config/soleil_ii/p.yaml b/examples/use_cases/config/soleil_ii/p.yaml index d8829096..3eab9404 100644 --- a/examples/use_cases/config/soleil_ii/p.yaml +++ b/examples/use_cases/config/soleil_ii/p.yaml @@ -14,8 +14,8 @@ controls: - type: tango.pyaml.controlsystem name: live tango_host: localhost:11000 - catalog: catalogs.yaml -arrays: arrays.yaml + catalog: ${include:catalogs.yaml} +arrays: ${include:arrays.yaml} devices: -- devices.yaml -- tuning_tools.yaml +- ${include:devices.yaml} +- ${include:tuning_tools.yaml} From bad4edc0f0da527f6ec83fa85d0853c06cd180e4 Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Wed, 7 Oct 2026 13:19:39 +0200 Subject: [PATCH 44/49] Fix dt4acc_twin_smoke test to resolve catalog via ConfigurationManager instead of assuming a bare filename --- tests/integration/test_dt4acc_twin_smoke.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/integration/test_dt4acc_twin_smoke.py b/tests/integration/test_dt4acc_twin_smoke.py index 1e891627..d9edec86 100644 --- a/tests/integration/test_dt4acc_twin_smoke.py +++ b/tests/integration/test_dt4acc_twin_smoke.py @@ -3,7 +3,6 @@ import numpy as np import pytest -import yaml from pyaml_test_lattice import configurations, lattices from pyaml.accelerator import Accelerator @@ -37,22 +36,17 @@ def _readback_value(device_access): def _load_fragment(config_key: str) -> dict: """Load one pyaml-test-lattice merged config fragment as a plain dict. - Bypasses pyaml's own YAML loader (which auto-loads any bare - ``*.yaml``/``*.json`` string value as a nested file relative to the - parent file's directory) since the lattice file ships under a different + The ``lattice`` reference is overridden since it lives in a different package subtree (``data/lattice``) than the config fragment - (``data/configuration``). The ``lattice`` and ``catalog`` references are - resolved explicitly instead. + (``data/configuration``). """ config_path = Path(configurations[config_key]) - fragment = yaml.safe_load(config_path.read_text()) + manager = ConfigurationManager() + manager.add(config_path) + fragment = manager.to_dict() fragment["simulators"][0]["lattice"] = str(Path(lattices[FODO_LATTICE_KEY])) - catalog_name = fragment["controls"][0]["catalog"] - catalog_path = config_path.parent / catalog_name - fragment["controls"][0]["catalog"] = yaml.safe_load(catalog_path.read_text()) - return fragment From 1fcfb64bd9764047f6a6c341d1fc0f2d3855c5f2 Mon Sep 17 00:00:00 2001 From: PONS Date: Thu, 8 Oct 2026 11:07:12 +0200 Subject: [PATCH 45/49] Support for transfer line --- pyaml/lattice/abstract_impl.py | 25 ++- pyaml/lattice/simulator.py | 19 +- pyaml/lattice/twiss_in.py | 52 ++++++ tests/config/tl2/tl2.mat | Bin 0 -> 69648 bytes tests/config/tl2/tl2.yml | 215 +++++++++++++++++++++++ tests/tuning_tools/test_transfer_line.py | 47 +++++ 6 files changed, 348 insertions(+), 10 deletions(-) create mode 100644 pyaml/lattice/twiss_in.py create mode 100644 tests/config/tl2/tl2.mat create mode 100644 tests/config/tl2/tl2.yml create mode 100644 tests/tuning_tools/test_transfer_line.py diff --git a/pyaml/lattice/abstract_impl.py b/pyaml/lattice/abstract_impl.py index 615d7ea0..a734a587 100644 --- a/pyaml/lattice/abstract_impl.py +++ b/pyaml/lattice/abstract_impl.py @@ -708,13 +708,14 @@ class BPMScalarAggregator(ScalarAggregator): Return the value unit. """ - def __init__(self, ring: at.Lattice): + def __init__(self, ring: at.Lattice, twiss_in: dict): """ Initialize the BPMScalarAggregator. """ self._lattice = ring self._refpts = [] self._matrices = [] + self._twiss_in = twiss_in def add_elem(self, elem: at.Element): """ @@ -764,9 +765,15 @@ def unit(self) -> str: def _transform(self) -> np.array: """Transform the configured value for the lattice interface.""" - _, orbit = at.find_orbit(self._lattice, refpts=self._refpts) + if self._twiss_in is None: + _, orbit = at.find_orbit(self._lattice, refpts=self._refpts) + pts = orbit[:, [0, 2]] # Extract x,y + else: + _, _, elemdata = at.get_optics(self._lattice, refpts=self._refpts, twiss_in=self._twiss_in) + pts = [e.closed_orbit[[0, 2]] for e in elemdata] + ones = np.ones(len(self._refpts)) - pts = orbit[:, [0, 2]] # Extract x,y + # Transfom BPM coordinates (offset and tilt) # Batch matrices multiplication (homogeneous coordinates) return np.matmul(self._matrices, np.column_stack([pts, ones])[:, :, None]).squeeze(-1) @@ -856,19 +863,25 @@ class RBpmArray(abstract.ReadFloatArray): Return the value unit. """ - def __init__(self, element: at.Element, lattice: at.Lattice): + def __init__(self, element: at.Element, lattice: at.Lattice, twiss_in: dict): """ Initialize the RBpmArray. """ self._element = element self._lattice = lattice + self._twiss_in = twiss_in # Gets the value def get(self) -> np.array: """Return the current value.""" index = self._lattice.index(self._element) - _, orbit = at.find_orbit(self._lattice, refpts=index) - pts = orbit[0, [0, 2]] + if self._twiss_in is None: + _, orbit = at.find_orbit(self._lattice, refpts=index) + pts = orbit[0, [0, 2]] + else: + _, _, elemdata = at.get_optics(self._lattice, refpts=index, twiss_in=self._twiss_in) + pts = elemdata[0].closed_orbit[[0, 2]] + # Transfom BPM coordinates (offset and tilt) return np.dot(self._element._transform, np.hstack([pts, 1])) # Use homogeneous coordinates # Gets the unit of the value diff --git a/pyaml/lattice/simulator.py b/pyaml/lattice/simulator.py index 738ddaf2..e0922ae3 100644 --- a/pyaml/lattice/simulator.py +++ b/pyaml/lattice/simulator.py @@ -48,6 +48,7 @@ from ..tuning_tools.tuning_tool import TuningTool from ..validation import DynamicValidation, register_schema from .lattice_elements_linker import LatticeElementsLinker +from .twiss_in import TwissIn if TYPE_CHECKING: from ..configuration.unbound_element import UnboundElement @@ -79,6 +80,8 @@ class Simulator(ElementHolder, DynamicValidation): mat_key : str, optional Variable name of the lattice when loading a MATLAB ``.mat`` lattice file. + twiss_in : TwissIn, optional + Describe input beam parameters of a tranfer line, when not specified the lattice is considered as a ring linker : LatticeElementsLinker, optional Custom linker used to associate PyAML elements with PyAT lattice elements. If omitted, elements are matched by name. @@ -123,6 +126,7 @@ def __init__( name: str, lattice: str, mat_key: str | None = None, + twiss_in: TwissIn | None = None, linker: LatticeElementsLinker | None = None, description: str | None = None, ): @@ -135,6 +139,11 @@ def __init__( self._lattice = lattice self._mat_key = mat_key self.description = description + self.twiss_in = twiss_in + if twiss_in is None: + self._at_twiss_in = None + else: + self._at_twiss_in = self.twiss_in._to_at() path: Path = ROOT.get() / self._lattice @@ -226,9 +235,9 @@ def create_bpm_aggregators(self, bpms: list[BPM]) -> list[ScalarAggregator]: list[ScalarAggregator] Aggregators for combined, horizontal, and vertical BPM positions. """ - agg = BPMScalarAggregator(self.get_lattice()) - aggh = BPMHScalarAggregator(self.get_lattice()) - aggv = BPMVScalarAggregator(self.get_lattice()) + agg = BPMScalarAggregator(self.get_lattice(), self._at_twiss_in) + aggh = BPMHScalarAggregator(self.get_lattice(), self._at_twiss_in) + aggv = BPMVScalarAggregator(self.get_lattice(), self._at_twiss_in) for b in bpms: e = self.get_at_elems(b)[0] agg.add_elem(e) @@ -293,7 +302,9 @@ def _fill_bpm(self, bpm: BPM) -> None: raise PyAMLException(f"BPM {bpm.get_name()} offset must be a 2-element array.") update_bpm_transform_matrix(bpm_elt) self.diagnostic.bpm.add( - bpm.attach(self, RBpmArray(bpm_elt, self.ring), RWBpmOffsetArray(bpm_elt), RWBpmTiltScalar(bpm_elt)) + bpm.attach( + self, RBpmArray(bpm_elt, self.ring, self._at_twiss_in), RWBpmOffsetArray(bpm_elt), RWBpmTiltScalar(bpm_elt) + ) ) def _fill_rf_plant(self, rf_plant: RFPlant) -> None: diff --git a/pyaml/lattice/twiss_in.py b/pyaml/lattice/twiss_in.py new file mode 100644 index 00000000..51ae9899 --- /dev/null +++ b/pyaml/lattice/twiss_in.py @@ -0,0 +1,52 @@ +""" +Initial input beam parameters for a transfer line +""" + +import numpy + +from ..validation import DynamicValidation, register_schema + +# Define the main class name for this module +PYAMLCLASS = "TwissIn" + + +@register_schema +class TwissIn(DynamicValidation): + """ + Class that describes input beam parameters of a transfer line + + Attributes + ---------- + alpha : list[float] + Initial alpha parameters (x,y) + beta : list[float] | None + Initial beta parameters (x,y), default [0,0] + position : list[float] | None + Initial position (6D), default [0,0,0,0,0,0] + dispersion : list[float] | None + Initial dispersion (4D), default [0,0,0,0] + """ + + def __init__( + self, + alpha: list[float], + beta: list[float] = None, + position: list[float] = None, + dispersion: list[float] = None, + ): + """ + Create TwissIn object + """ + super().__init__() + self.alpha = alpha + self.beta = beta if beta is not None else [0.0, 0.0] + self.position = position if position is not None else [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + self.dispersion = dispersion if dispersion is not None else [0.0, 0.0, 0.0, 0.0] + + def _to_at(self) -> dict: + return { + "beta": numpy.array(self.alpha, dtype=float), + "alpha": numpy.array(self.beta, dtype=float), + "closed_orbit": numpy.array(self.position, dtype=float), + "dispersion": numpy.array(self.dispersion, dtype=float), + } diff --git a/tests/config/tl2/tl2.mat b/tests/config/tl2/tl2.mat new file mode 100644 index 0000000000000000000000000000000000000000..b871250f4aad43cb45c7b86cf12aea470f06e401 GIT binary patch literal 69648 zcmeHQYmi(;6~2V9*c5gN#v$N~n zS!xq}019gP1C)Y7@l8<#r6|f$pb!lT5(HA>f6XuZ2w-vYmrnenxmafeB* zaq?pY{4bWi%+yrMDQuY-h03bDiBctLp^ zUR>V)y(o7gytq7mFV=fU;TGVYuos}}wV&9I2T#~^paa`+7JLbBM>XTb+qDd2^M?k> zvRr4XFyyci8QGF?$BM(p+iAle{CldsU)KR8&t9ILf|2FdA>J>FtMnI0zqUhd++Vv$ zO`f&ihL!~NBkG!{H&`FY=Eq=uWX9odEZB}MHIDlUAcNOk4liMT3bRv~n`N_%eP7T# z!|S3>l-j+}>K=YFaE3=6#L z6yQgbbQu*V!vUTZ(Ni0H?~deZ>*;>?q!Y}8vRcA^{U$be(24!K5^Zz z{-~9HZwKRn?MFLTiyaw;&kJ~B34l^&?NgbVb?zvRu4S#A9`DT;(gkO7%IcfQZOu=N zci{hve|O^FA_QSSSHd6veCQf%BF~2_mfUsmv)jJtjZY7JH}4mj9xsLWMLouPy5OeZ zUYzvS@yVPs0@km^LnErbz$Lw+Ccn&9lb?;F)#|(0ej6%5{VB75sp(uHI|&_Hyb#{U zBy>8zPBr?fYX3yPMWzQ*`!Uee9EY}2`+e`NlNT)7`?gj%p?+*t@J&5c9A|Wj9mofh9ctwMEhXuVu@#x zW<6Z1FOlneEglKZ5lm)uU2;O z_!{Fz++O4W-y6Xrt%mFJB63RjKlDFp<(2xAbi8Qx%6@OW^x>6M;~ESvZ0Jkw7vOI! z==&j`h;|$~8g9=KZ_qdJ7aR{8UexP=Q@HQ9Z$0bvFU21h<;8Z${F6n{PJTalFIexN z&WyU#C87%NM>qVzdnvWsi_^4F{qO@wW54-7t?c@v&yIorNjm6+b@7 zaDm%16fo-_v;N`wFbw+82>(X(X;43F^=Grp;`ZQ)A7Qrf7lFf)cWU@O`R9Myzhw}RDWKSuY^AlTK}|C z`|GJ?$6j?~*XvrXe_pGRf5Cgl_271RiT;UImdWGG+_gHL{3s+PcI3w}DPvW>;AZk8 z4y&%3$rg%lz%|9ZpYqI~L-{@(@G&T2_K#(WyiIvF&}{oEvyW&yOY2-_OS^mfhI%0( zgF4<16mB?m;oyO)#+&ym-Wjd~u#Ua9CYyIME=x|Nw&SPQA8fzIC;Gy&eG*^4q%~<1Cus5yBKyiaGVVXzueTOl6U&e zZ(upjM!m7&6@<4k9-4O(pM?@tAt$okl;7PM;@+ zM`D(_Q9M$tZZxQ7y*^jXDNcH5gy)>Su^~&RkT%O+`9dAsWtAsb0&%=w_ zx`M4sY`9ph{-rF=rJH|f^4=LgfE+C6VUK}zldOBCU(~+R( zChvU?_DA;^dx-vX$(}_k?>!ti(bTTOQ^mam_&rte#D=G0@sLCNqZQ^qOdGFM!w=TW zE=Gi>YFFW@AlVu+uK%r-zMD80^KYg8KIR+1Orfrmbjde35pjFTG9&VRmvD$X%^ zdcJ_Fyf|+td1*}E;B1(17S4G*k2>$x@4xMqhhu-%%AbvX+?CFUnz$=Ns7A$IU-REAigCt%Ia- zz5#R24UdBi1K5fC$hCPMM31qDXj@A5EL!?zjik}zf%`Ik327xq|Xn-O92=|X;eIDX4`Gc zyG~>K!YJ`{QQn4j2HA~j<3YONkKv`t_t$9ehY!-%r-qk8w9)&#$s=LCZk|6=`#H18 zG~Lf&T0ggOV#7;Wmf43l2XUGnV-F24#Sm^T_D4&2DRQ3tz4q15?Y#K~t@5T!o_uhg zd@raPm4`I@kr;ojnx`-NGn92blQ((tVak(-^G}q!L>XVv0s)#z4X17h4pm7O~KPOob=Z5$(%D% z$d0sl51>%9i#04kY{&AD_7mk&m?`XvS zB9q6#`dR9insa5n^}A{whN?VF1+P%&9g{ZkMKQjp+dABjvyjI@#@V8}cz&V{6>cA8 zILp^vDAUMwxWUpjiBVQmy_izm}TaVC&5$xL6R5`0g!rR2YJcrq-Tsx1 zVZF}nh~D#S>3p@x`zYa!$@`ccr;_mxQr>YO-lmdQ{jG;%;F%koz+EYjkGVBJG2Vfi zS^T>b|L)3Vrf|;+7R2L9>$}IsgGqavyBhje! z%<4yvyWp0OeU}^$7v*heY7k#o?RrKxK1=Se48_~we*HLBtzLVN$+$+ZePB`JRs8&P ze>eS{RD(l}#i2_VZ(Q+{NADr?Dx!S7a3}XoO0 z-@$n^%;&^7oWfQ8w+xQ82`cf7PQX@-u4RB zev9&z;&frf+3tY^`ME`#z4Dw^{;Y1iHP=(rCA14zXL*=$^0~ZtEiC@|fjWpqoFDbF zZ_&uZOf;$FH{5qRMht$TmA)SV@dENZG5it3gP`??4IiP}*AGJ$=I49%@1WXOQC@6^ z49BXCTk6kWC&+xL7oNHNuLghSmcJ&S>um6+vH0`e=Qb|AclU$de4y|sqI|vZM>qdd zo;Q(*H^b0Saa%mEvQodH?GbqqtT*C3f#~If)Ayg4d~i@QD5z_tJW;y7dl!Qr)vUK< z`X1hM026VQJWs|C{7EZ+C2}~?V{oE;o|}Adw6}Ac51vS<@G9(hv^`qkgl>M^VR^)4 zJXGhEM1)67sMtmCIIgqJ{)a~HfAGg+An%wwPs}==dBA9AF^1tUe!sZZINkoX_dWRW z7~3~US;vd=HZ+d&nGxp^$-LB(yDolq+Y;lK2jP#n=L+?^Qv7$;KFvY;ek-$nX9!d7 z*PHcw5Uw4D=flT+ka@n`OK;!t`XBTW%d<|@7)g18zvUyz-z zq}RCxFM~nh^Br@Xu!wmbxW?zaIzCq#bq(v=;U(+?hnlbz9BRUDaHwH6Xf{9Aksr%B zmbEHha5MQ4hgDb2WDCVN;FGD$%sO`zvl+3hoi05I=g|2E9qc{vv;)f$_mh;Le!%p* zR=b)8Mv^+OS{{4mreCjqwJM&0_b1*H`n*`j-ddB*I~mt|ycW3~cb>UtLvr73!%Q2((Gxq@v&K=e_^{Yvk-n?I^*{>kDfjffk@ zV3BBW)&dJ6j`u>VKN(*`9p{fcwd~ldZhX=k=bobOAb%-akISBC*qI-#>+=k+-L&U{ z{YyT(gL*$qeth2Kq3fV5wyEuB@-3eHMyPOo$KO~)zd~F`;2b>5`$Tm=uziibK7}9R zt;hCN@;ChRAT0Z35Syv`p(8FE|CiyhpI1sl20wN1 zou{iBXZ#LveTDUU@e7ZAaM6WJH{9g4gMv>?{@QtyH~7kWKo%=`~SLaS@Qq@ literal 0 HcmV?d00001 diff --git a/tests/config/tl2/tl2.yml b/tests/config/tl2/tl2.yml new file mode 100644 index 00000000..c722ef6e --- /dev/null +++ b/tests/config/tl2/tl2.yml @@ -0,0 +1,215 @@ +type: pyaml.accelerator +facility: ESRF +machine: tl2 +energy: 6e9 + +simulators: + - type: pyaml.lattice.simulator + lattice: tl2.mat + name: design + twiss_in: + class: pyaml.lattice.twiss_in.TwissIn + alpha: [-1.71, 1.47] + beta: [5.9 , 5.65] + dispersion: [-0.145, -0.092, 0, 0] + +data_folder: /data/store + +arrays: + + - type: pyaml.arrays.magnet + name: HCORR + elements: + - CH* + - type: pyaml.arrays.magnet + name: VCORR + elements: + - CV* + - type: pyaml.arrays.bpm + name: BPMS + elements: + - BPM* + +devices: + + - class: pyaml.tuning_tools.orbit.Orbit + bpm_array_name: BPMS + hcorr_array_name: HCORR + vcorr_array_name: VCORR + name: DEFAULT_ORBIT_CORRECTION + singular_values: 5 + response_matrix: ideal_tl2_orm.json + + - class: pyaml.tuning_tools.orbit_response_matrix.OrbitResponseMatrix + bpm_array_name: BPMS + hcorr_array_name: HCORR + vcorr_array_name: VCORR + corrector_delta: 1e-6 + name: DEFAULT_ORBIT_RESPONSE_MATRIX + + - class: pyaml.diagnostics.bpm.bpm.BPM + name: BPM_CV1 + x_pos: tl2/d-bpm/cv1/X_Position + y_pos: tl2/d-bpm/cv1/Z_Position + - class: pyaml.diagnostics.bpm.bpm.BPM + name: BPM_CH3 + x_pos: tl2/d-bpm/d2/X_Position + y_pos: tl2/d-bpm/d2/Z_Position + - class: pyaml.diagnostics.bpm.bpm.BPM + name: BPM_QD5 + x_pos: tl2/d-bpm/q5/X_Position + y_pos: tl2/d-bpm/q5/Z_Position + - class: pyaml.diagnostics.bpm.bpm.BPM + name: BPM_QF6 + x_pos: tl2/d-bpm/q6/X_Position + y_pos: tl2/d-bpm/q6/Z_Position + - class: pyaml.diagnostics.bpm.bpm.BPM + name: BPM_SX + x_pos: tl2/d-bpm/sx/X_Position + y_pos: tl2/d-bpm/sx/Z_Position + - class: pyaml.diagnostics.bpm.bpm.BPM + name: BPM_QD8 + x_pos: tl2/d-bpm/q8/X_Position + y_pos: tl2/d-bpm/q8/Z_Position + - class: pyaml.diagnostics.bpm.bpm.BPM + name: BPM_QD12 + x_pos: tl2/d-bpm/q12/X_Position + y_pos: tl2/d-bpm/q12/Z_Position + - class: pyaml.diagnostics.bpm.bpm.BPM + name: BPM_QF14 + x_pos: tl2/d-bpm/q14/X_Position + y_pos: tl2/d-bpm/q14/Z_Position + - class: pyaml.diagnostics.bpm.bpm.BPM + name: BPM_S1PM + x_pos: tl2/d-bpm/s1pm/X_Position + y_pos: tl2/d-bpm/s1pm/Z_Position + + - class: pyaml.magnet.hcorrector.HCorrector + name: CH1 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/ch1/Current + - class: pyaml.magnet.hcorrector.HCorrector + name: CH2 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/ch2/Current + - class: pyaml.magnet.hcorrector.HCorrector + name: CH3 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/ch3/Current + - class: pyaml.magnet.hcorrector.HCorrector + name: CH4 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/ch4/Current + - class: pyaml.magnet.hcorrector.HCorrector + name: CH5 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/ch5/Current + - class: pyaml.magnet.hcorrector.HCorrector + name: CH6 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/ch6/Current + - class: pyaml.magnet.hcorrector.HCorrector + name: CH7 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/ch7/Current + - class: pyaml.magnet.hcorrector.HCorrector + name: CH8 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/ch8/Current + + - class: pyaml.magnet.vcorrector.VCorrector + name: CV0 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/cv0/Current + - class: pyaml.magnet.vcorrector.VCorrector + name: CV1 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/cv1/Current + - class: pyaml.magnet.vcorrector.VCorrector + name: CV3 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/cv3/Current + - class: pyaml.magnet.vcorrector.VCorrector + name: CV4 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/cv4/Current + - class: pyaml.magnet.vcorrector.VCorrector + name: CV5 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/cv5/Current + - class: pyaml.magnet.vcorrector.VCorrector + name: CV6 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/cv6/Current + - class: pyaml.magnet.vcorrector.VCorrector + name: CV7 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/cv7/Current + - class: pyaml.magnet.vcorrector.VCorrector + name: CV8 + model: + class: pyaml.magnet.linear_model.LinearMagnetModel + calibration_factor: 161e-6 + unit: 1/m + hardware_unit: A + powerconverter: tl2/ps-c1/cv8/Current diff --git a/tests/tuning_tools/test_transfer_line.py b/tests/tuning_tools/test_transfer_line.py new file mode 100644 index 00000000..cd4dfe3e --- /dev/null +++ b/tests/tuning_tools/test_transfer_line.py @@ -0,0 +1,47 @@ +import numpy +import pytest + +from pyaml.accelerator import Accelerator + + +def test_transfer_line(): + tl2 = Accelerator.load("tests/config/tl2/tl2.yml") + tl2.design.tool.orm.measure() + orm_data = tl2.design.tool.orm.get() + mat = numpy.array(orm_data["matrix"]) + + orm_hv = mat[:9, 8:] + orm_vh = mat[9:, :8] + assert numpy.allclose(orm_hv, 0) + assert numpy.allclose(orm_vh, 0) + + orm_h = mat[:9, :8] + orm_v = mat[9:, 8:] + + assert numpy.allclose( + orm_h[8, :], [1.49746082, 3.11487458, 10.60522905, -0.84014865, -19.27774502, -14.92314963, 1.29287711, 1.60223207] + ) + assert numpy.allclose( + orm_v[8, :], [10.95115436, 10.56436269, 16.43975007, 32.48697172, 12.7218589, 10.31009581, 10.46821268, 2.60568252] + ) + + tl2.design.tool.orm.save("tl2.json") + tl2.design.tool.orbit.load("tl2.json") + + # Mangle the orbit + hcorr = tl2.design.magnets.get("HCORR") + vcorr = tl2.design.magnets.get("VCORR") + numpy.random.seed(1) + std_kick = 1e-6 # rad + hcorr.strengths.set(hcorr.strengths.get() + std_kick * numpy.random.normal(size=len(hcorr))) + vcorr.strengths.set(vcorr.strengths.get() + std_kick * numpy.random.normal(size=len(vcorr))) + + orbit = tl2.design.diagnostic.bpms.get("BPMS").positions + assert numpy.allclose(numpy.std(orbit.get(), axis=0), [1.78601145e-05, 3.57671504e-05]) + + tl2.design.tool.orbit.correct() + tl2.design.tool.orbit.correct() + + assert numpy.allclose(numpy.std(orbit.get(), axis=0), [7.50786048e-07, 7.26005222e-07]) + + assert numpy.allclose(orbit.get()[2], tl2.design.diagnostic.bpm.get("BPM_QD5").positions.get()) From 1b3c17f750b7ea7d99aaef439acd0555706dcc37 Mon Sep 17 00:00:00 2001 From: PONS Date: Thu, 8 Oct 2026 11:18:33 +0200 Subject: [PATCH 46/49] Fix typing --- pyaml/lattice/twiss_in.py | 6 +++--- tests/config/tl2/tl2.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyaml/lattice/twiss_in.py b/pyaml/lattice/twiss_in.py index 51ae9899..c42ee6c7 100644 --- a/pyaml/lattice/twiss_in.py +++ b/pyaml/lattice/twiss_in.py @@ -30,9 +30,9 @@ class TwissIn(DynamicValidation): def __init__( self, alpha: list[float], - beta: list[float] = None, - position: list[float] = None, - dispersion: list[float] = None, + beta: list[float] | None = None, + position: list[float] | None = None, + dispersion: list[float] | None = None, ): """ Create TwissIn object diff --git a/tests/config/tl2/tl2.yml b/tests/config/tl2/tl2.yml index c722ef6e..8509e34d 100644 --- a/tests/config/tl2/tl2.yml +++ b/tests/config/tl2/tl2.yml @@ -4,7 +4,7 @@ machine: tl2 energy: 6e9 simulators: - - type: pyaml.lattice.simulator + - class: pyaml.lattice.simulator.Simulator lattice: tl2.mat name: design twiss_in: From 620bb9cafefb7e50519fa2c1d86ec53b52cdd847 Mon Sep 17 00:00:00 2001 From: PONS Date: Thu, 8 Oct 2026 14:39:36 +0200 Subject: [PATCH 47/49] Fix TL2 tests, usage of ThinMPolePass and corrected calibration factor --- tests/config/tl2/tl2.mat | Bin 69648 -> 68152 bytes tests/config/tl2/tl2.yml | 32 +++++++++++------------ tests/tuning_tools/test_transfer_line.py | 8 +++--- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/config/tl2/tl2.mat b/tests/config/tl2/tl2.mat index b871250f4aad43cb45c7b86cf12aea470f06e401..0d1a909d4df5e79e438be780169728693cc448af 100644 GIT binary patch delta 2652 zcmbQRfMv%NmI;OuAsM9#{>dc@3Kj~6CRT>VR;CsTMg~S^69aW7CU8uwWoO#Jxv@=- zgDHS%@;M$eM&HT3EGlxoi531uDXB#a3=9GJIhA?&xsC{?)6{-O)5$dgLX*olttKyE z(^26CN-_d52nYZ%h#!)Xndb}CoEnf=Tnyw-PMF#<`3zTrB2LAnIVG6|Kn;_9xnECi zU}j_dv)P`fmYEEf`H<`?C4#PE*z6}-$2j?p)1u97B5q71yQ)W=bXO4$tIf8y<&2x( zNIf7u0((?w=pgkIVkCzUC`Gl|lb)i8jl0cvoL-OHr;c=25fx^uCeLrMWCw@atEZd&eKc4%v$vJ9qT4Po@>jt&i!GP$hysOQU#2gr_H}7N^+=yiZ!=w zG|3LKJ6=x?P+((xIN6@3mt=>dc@3g!xiMpmY#Rz?O2Mg~S^69aW7CU8uwWoPQ(-`FO{ z!L)*DavYP{#&IM`j+ODWESM- zq%r`(+75OL-QFoACvmWj_j&4VnUPKlySHU z&6_~O*dT^&{-+uyj%*Alw8ZRi7(>W&#~e{Tx5tGz&&9gEW;X;o=rStIj?q}5VBEwCXntNzsZ=+X`7D2IhfvBGx^X=yU8CO zPMJI>^wH+B+4n?|9RyBCn@BJ$Xp0^DoFfVwiZUKf?icMvHVYI^&$g0ameLM8)XZ{i z=RFl<6G5&kyN|;}e$*_?Fj@b~n#r3V+OnsC!8L5ob9+pRVa-orMa<>R`j>!3%-?6n j(NY&MGruFn$ip9O*>j%%UitCTS)h@ojGJvf{SgNM*!Je! diff --git a/tests/config/tl2/tl2.yml b/tests/config/tl2/tl2.yml index 8509e34d..8fef60f8 100644 --- a/tests/config/tl2/tl2.yml +++ b/tests/config/tl2/tl2.yml @@ -88,7 +88,7 @@ devices: name: CH1 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/ch1/Current @@ -96,7 +96,7 @@ devices: name: CH2 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/ch2/Current @@ -104,7 +104,7 @@ devices: name: CH3 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/ch3/Current @@ -112,7 +112,7 @@ devices: name: CH4 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/ch4/Current @@ -120,7 +120,7 @@ devices: name: CH5 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/ch5/Current @@ -128,7 +128,7 @@ devices: name: CH6 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/ch6/Current @@ -136,7 +136,7 @@ devices: name: CH7 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/ch7/Current @@ -144,7 +144,7 @@ devices: name: CH8 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/ch8/Current @@ -153,7 +153,7 @@ devices: name: CV0 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/cv0/Current @@ -161,7 +161,7 @@ devices: name: CV1 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/cv1/Current @@ -169,7 +169,7 @@ devices: name: CV3 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/cv3/Current @@ -177,7 +177,7 @@ devices: name: CV4 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/cv4/Current @@ -185,7 +185,7 @@ devices: name: CV5 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/cv5/Current @@ -193,7 +193,7 @@ devices: name: CV6 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/cv6/Current @@ -201,7 +201,7 @@ devices: name: CV7 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/cv7/Current @@ -209,7 +209,7 @@ devices: name: CV8 model: class: pyaml.magnet.linear_model.LinearMagnetModel - calibration_factor: 161e-6 + calibration_factor: 3.222e-3 unit: 1/m hardware_unit: A powerconverter: tl2/ps-c1/cv8/Current diff --git a/tests/tuning_tools/test_transfer_line.py b/tests/tuning_tools/test_transfer_line.py index cd4dfe3e..7c5b422c 100644 --- a/tests/tuning_tools/test_transfer_line.py +++ b/tests/tuning_tools/test_transfer_line.py @@ -19,10 +19,10 @@ def test_transfer_line(): orm_v = mat[9:, 8:] assert numpy.allclose( - orm_h[8, :], [1.49746082, 3.11487458, 10.60522905, -0.84014865, -19.27774502, -14.92314963, 1.29287711, 1.60223207] + orm_h[8, :], [1.49217029, 3.11318219, 10.60494678, -0.82242986, -19.2814236, -14.92682822, 1.28503979, 1.60584401] ) assert numpy.allclose( - orm_v[8, :], [10.95115436, 10.56436269, 16.43975007, 32.48697172, 12.7218589, 10.31009581, 10.46821268, 2.60568252] + orm_v[8, :], [10.95203344, 10.56524176, 16.43659334, 32.50321299, 12.72373742, 10.3122307, 10.47365958, 2.61214668] ) tl2.design.tool.orm.save("tl2.json") @@ -37,11 +37,11 @@ def test_transfer_line(): vcorr.strengths.set(vcorr.strengths.get() + std_kick * numpy.random.normal(size=len(vcorr))) orbit = tl2.design.diagnostic.bpms.get("BPMS").positions - assert numpy.allclose(numpy.std(orbit.get(), axis=0), [1.78601145e-05, 3.57671504e-05]) + assert numpy.allclose(numpy.std(orbit.get(), axis=0), [1.78288459e-05, 3.57986943e-05]) tl2.design.tool.orbit.correct() tl2.design.tool.orbit.correct() - assert numpy.allclose(numpy.std(orbit.get(), axis=0), [7.50786048e-07, 7.26005222e-07]) + assert numpy.allclose(numpy.std(orbit.get(), axis=0), [7.51291713e-07, 7.24545959e-07]) assert numpy.allclose(orbit.get()[2], tl2.design.diagnostic.bpm.get("BPM_QD5").positions.get()) From 4d9e01478918947e5ddc8457fa62c3ef7eaebc10 Mon Sep 17 00:00:00 2001 From: PONS Date: Tue, 13 Oct 2026 15:13:50 +0200 Subject: [PATCH 48/49] Update action comments --- pyaml/common/constants.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyaml/common/constants.py b/pyaml/common/constants.py index 4f8703a2..4d4ee5ac 100644 --- a/pyaml/common/constants.py +++ b/pyaml/common/constants.py @@ -20,15 +20,17 @@ class Action(Enum): Attributes ---------- APPLY : int - Callback triggered immediately after an actuator excitation. + Callback triggered after an actuator excitation completes (including the waiting time). RESTORE : int Callback triggered immediately after restoring the actuator. MEASURE : int Callback triggered immediately after taking a measurement. + INIT : int + Triggered once before the measurement starts """ APPLY = 0 - "Triggered immediately after actuator excitation." + "Triggered after an actuator excitation completes (including the waiting time)." RESTORE = 1 "Triggered immediately after restoring the actuator to its initial value." MEASURE = 2 From d939f7edfb34a4e995b05585b786a46491adf9e9 Mon Sep 17 00:00:00 2001 From: Vadim Gubaidulin Date: Thu, 15 Oct 2026 12:01:02 +0200 Subject: [PATCH 49/49] Revert "Feature: template in configuration file, 2nd variation" --- examples/template/build_template_example.py | 7 - examples/template/templated_config.yaml | 29 --- pyaml/configuration/fileloader.py | 149 ++---------- pyaml/configuration/manager.py | 16 +- pyaml/configuration/template.py | 156 ------------- tests/configuration/test_templates.py | 240 -------------------- 6 files changed, 19 insertions(+), 578 deletions(-) delete mode 100644 examples/template/build_template_example.py delete mode 100644 examples/template/templated_config.yaml delete mode 100644 pyaml/configuration/template.py delete mode 100644 tests/configuration/test_templates.py diff --git a/examples/template/build_template_example.py b/examples/template/build_template_example.py deleted file mode 100644 index 7572cb83..00000000 --- a/examples/template/build_template_example.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Load and print templated devices; run from this example's directory.""" - -from pyaml.accelerator import Accelerator - -sr = Accelerator.load("templated_config.yaml") -for dev in sr._devices: - print(dev) diff --git a/examples/template/templated_config.yaml b/examples/template/templated_config.yaml deleted file mode 100644 index 71be2cc3..00000000 --- a/examples/template/templated_config.yaml +++ /dev/null @@ -1,29 +0,0 @@ -class: pyaml.accelerator.Accelerator -machine: sr -facility: PETRAIII -energy: 6e9 -controls: - - class: tango.pyaml.controlsystem.TangoControlSystem - tango_host: ebs-simu-3:10000 - name: live - catalog: - class: tango.pyaml.tango_catalog.TangoCatalog - disconnected: True -devices: -- ${template:SHI,SHI,01} -- ${template:SHI,SHI,02} -- ${template:SHI,SHI,03} -templates: - - name: SHI - parameters: - - sh_name - - ii - config: - class: pyaml.magnet.hcorrector.HCorrector - name: "{sh_name}-{ii}" - model: - class: pyaml.magnet.linear_model.LinearMagnetModel - unit: rad - hardware_unit: str - calibration_factor: 1.0 - powerconverter: tango/MAGNET/{sh_name}-{ii}/B1L diff --git a/pyaml/configuration/fileloader.py b/pyaml/configuration/fileloader.py index 10ace630..c8b09aa9 100644 --- a/pyaml/configuration/fileloader.py +++ b/pyaml/configuration/fileloader.py @@ -1,9 +1,8 @@ """ Load PyAML configuration files and expand nested references. -The loader supports YAML and JSON files, parameterized templates, environment -and path resolvers, recursive file includes, and optional source-location -metadata for diagnostics. +The loader supports YAML and JSON files, environment and path resolvers, +recursive file includes, and optional source-location metadata for diagnostics. """ import io @@ -24,7 +23,6 @@ from yaml.loader import SafeLoader from .. import PyAMLException -from .template import TemplateManager logger = logging.getLogger(__name__) @@ -139,11 +137,6 @@ class LoadContext: Preserve source locations in loaded mappings. include_stack : list[pathlib.Path], optional Active include chain. Usually left empty for a new session. - expand : callable or None, optional - Callback used to expand generated configurations. Installed temporarily - by the active file loader and restored when that loader finishes. - templates : TemplateManager, optional - Template registry shared by files in this session. Defaults to a fresh registry. Methods ------- @@ -153,9 +146,6 @@ class LoadContext: include_locations: bool = False include_stack: list[Path] = field(default_factory=list) - expand: Callable[[Any], Any] | None = None - # Is populated with ConfigLoader's expand function for use in template resolver - templates: TemplateManager = field(default_factory=TemplateManager) @contextmanager def loading(self, path: Path): @@ -302,86 +292,16 @@ def resolve_include(value: str, context: LoadContext | None = None) -> Any: return _load(value, context) -@resolver("template") -def resolve_template(value: str, context: LoadContext | None = None) -> Any: +def load(filename: str, include_locations: bool = False) -> Union[dict, list]: """ - Instantiate a template and recursively expand its configuration. + Load a configuration file. - Parameters - ---------- - value : str - Name and comma-separated string arguments in the format - ``NAME,ARG1,ARG2,...``, or ``NAME`` for no arguments. A trailing comma - supplies an empty-string argument. Commas in arguments are not escaped. - context : LoadContext or None, optional - Active loading context providing the template registry and expansion - callback. Required for resolution despite the compatibility default. - - Returns - ------- - object - Parsed and expanded configuration data. - - Raises - ------ - PyAMLException - If the active context is missing, the template name is unknown, or - expansion exceeds the recursion limit. Expansion errors also propagate. - PyAMLConfigException - If the number of arguments does not match the template parameters. - """ - if context is None or context.expand is None: - raise PyAMLException("Template resolver requires an active loading context.") - - try: - name, separator, argument_text = value.partition(",") - arguments = argument_text.split(",") if separator else [] - try: - generated = context.templates.generate(name, *arguments) - return context.expand(generated) - except RecursionError as exc: - raise PyAMLException( - f"Recursion limit reached while expanding template {name!r}. " - "Check for circular template references or excessive nesting." - ) from exc - except KeyError as exc: - raise PyAMLException(f"Invalid template resolver call {value}.") from exc - - -def load(filename: str, include_locations: bool = False, *, templates: TemplateManager | None = None) -> Union[dict, list]: - """ - Load a configuration file, register templates, and expand references. - - Parameters - ---------- - filename : str - YAML or JSON filename. Relative paths are resolved against ``ROOT``. - include_locations : bool, optional - Preserve source-location metadata in YAML mappings. Defaults to False, - which uses the faster C-based YAML loader without location metadata. - templates : TemplateManager or None, optional - Registry to share across configuration fragments. If omitted, a fresh - registry is created. Included files share the same registry. - - Returns - ------- - dict or list - Expanded configuration with root-level template definitions removed. - - Raises - ------ - PyAMLException - If the file format is unsupported, parsing fails, or reference - expansion fails. - PyAMLConfigException - If template registration or argument-count validation fails. + When include_locations is False, uses the faster C-based YAML loader + and skips including source location metadata. """ # Create a new context - context = LoadContext( - include_locations=include_locations, - templates=templates if templates is not None else TemplateManager(), - ) + context = LoadContext(include_locations=include_locations) return _load(filename, context) @@ -399,12 +319,7 @@ def _load(filename: str, context: LoadContext) -> Union[dict, list]: else: raise PyAMLException(f"{filename} File format not supported (only .yaml .yml or .json)") - previous_expand = context.expand - context.expand = loader.expand - try: - return loader.load() - finally: - context.expand = previous_expand + return loader.load() class ConfigLoader(ABC): @@ -422,8 +337,6 @@ class ConfigLoader(ABC): ------- expand(obj) Recursively expand configuration values. - register_templates(config) - Register and remove root-level template definitions before expansion. load() Load and parse the current configuration file. """ @@ -586,34 +499,6 @@ def _expand_list(self, items: list) -> list: return expanded - def register_templates(self, config): - """ - Register root-level templates in the current loading context. - - Parameters - ---------- - config : object - Parsed configuration. For dictionaries, the ``templates`` section - is removed in place and its definitions are registered. Other - values are left unchanged. - - Raises - ------ - PyAMLConfigException - If a template name is already registered in this context's registry. - KeyError - If a definition lacks ``name``, ``parameters``, or ``config``. - """ - if not isinstance(config, dict): - return - - for template in config.pop("templates", []): - self.context.templates.add( - name=template["name"], - parameters=template["parameters"], - config=template["config"], - ) - @abstractmethod def load(self) -> Union[dict, list]: """Load and parse the current configuration file.""" @@ -634,7 +519,7 @@ class YAMLLoader(ConfigLoader): Methods ------- load() - Parse YAML, register root-level templates, and expand references. + Parse the YAML file and expand nested configuration references. """ def __init__(self, path: Path, context: LoadContext): @@ -644,15 +529,12 @@ def __init__(self, path: Path, context: LoadContext): self._loader = SafeLineLoader if context.include_locations else CLoader def load(self) -> Union[dict, list]: - """Parse YAML, register root-level templates, and expand configuration references.""" + """Parse the YAML file and expand nested configuration references.""" logger.log(logging.DEBUG, f"Loading YAML file '{self.path}'") with open(self.path) as file: try: - parsed_config = yaml.load(file, Loader=self._loader) - # "templates" is popped out here if it exists - self.register_templates(parsed_config) - return self.expand(parsed_config) + return self.expand(yaml.load(file, Loader=self._loader)) except yaml.YAMLError as exc: raise PyAMLException(f"{self.path}: {exc}") from exc @@ -671,7 +553,7 @@ class JSONLoader(ConfigLoader): Methods ------- load() - Parse JSON, register root-level templates, and expand references. + Parse the JSON file and expand nested configuration references. """ def __init__(self, path: Path, context: LoadContext): @@ -680,15 +562,12 @@ def __init__(self, path: Path, context: LoadContext): super().__init__(path, context) def load(self) -> Union[dict, list]: - """Parse JSON, register root-level templates, and expand configuration references.""" + """Parse the JSON file and expand nested configuration references.""" logger.log(logging.DEBUG, f"Loading JSON file '{self.path}'") with open(self.path) as file: try: - parsed_config = json.load(file) - # "templates" is popped out here if it exists - self.register_templates(parsed_config) - return self.expand(parsed_config) + return self.expand(json.load(file)) except json.JSONDecodeError as exc: raise PyAMLException(f"{self.path}: {exc}") from exc diff --git a/pyaml/configuration/manager.py b/pyaml/configuration/manager.py index 94998b94..68baf41c 100644 --- a/pyaml/configuration/manager.py +++ b/pyaml/configuration/manager.py @@ -24,7 +24,6 @@ from ..common.exception import PyAMLConfigException from .fileloader import ROOT, load from .restfetcher import REMOTE_BASE_URL_KEY, SourceRoot, fetch_remote_config, is_remote_url, resolve_reference -from .template import TemplateManager _INTERNAL_METADATA_KEYS = {"__location__", "__fieldlocations__", REMOTE_BASE_URL_KEY} _VALID_QUERY_KEY_RE = re.compile(r"^[A-Z0-9_]+$") @@ -60,7 +59,7 @@ class ConfigurationManager: replace(category, element) Replace an existing named entry in an aggregated category. clear(category=None) - Clear the aggregated state and templates, or a single root field/category. + Clear the aggregated state, or a single root field/category. categories() Return categories that currently contain entries. keys(category=None) @@ -157,8 +156,7 @@ def __init__(self): Initialize an empty configuration manager. The manager starts with the default accelerator type and empty named - categories. Source tracking is enabled as fragments are added. Local - file fragments share this manager's private template registry. + categories. Source tracking is enabled as fragments are added. """ self._state: dict[str, Any] = {"class_path": self.DEFAULT_CLASS_PATH} self._items_by_category: dict[str, dict[str, dict[str, Any]]] = {category: {} for category in self.NAMED_CATEGORIES} @@ -166,7 +164,6 @@ def __init__(self): self._field_sources: dict[str, str] = {} self._build_root: SourceRoot = ROOT.get() self._build_root_locked = False - self._templates = TemplateManager() def add(self, payload, **kwargs) -> None: r""" @@ -290,14 +287,12 @@ def replace(self, category: str, element: dict) -> None: def clear(self, category: str | None = None) -> None: r""" - Clear all configuration state and templates, or one root field/category. + Clear the aggregated state, or a single root field/category. Parameters ---------- category : str, optional - If provided, only that category or root field is cleared and - template definitions are retained. If omitted, all aggregated - state and this manager's template registry are cleared. + If provided, only that category or root field is cleared. Examples -------- @@ -311,7 +306,6 @@ def clear(self, category: str | None = None) -> None: >>> manager.clear() """ if category is None: - self._templates.clear() self._state = {"class_path": self.DEFAULT_CLASS_PATH} self._field_sources.clear() for name in self.NAMED_CATEGORIES: @@ -704,7 +698,7 @@ def _load_payload( source_root = resolved_path.parent try: ROOT.set(source_root) - fragment = load(resolved_path.name, include_locations, templates=self._templates) + fragment = load(resolved_path.name, include_locations) finally: ROOT.set(previous_root) diff --git a/pyaml/configuration/template.py b/pyaml/configuration/template.py deleted file mode 100644 index 7cf5d2a9..00000000 --- a/pyaml/configuration/template.py +++ /dev/null @@ -1,156 +0,0 @@ -""" -Store and instantiate parameterized configuration templates. - -Each registry owns its definitions. Generation substitutes positional arguments -into a copy of the template; the configuration loader expands any remaining -file, environment, or template references. -""" - -import copy -import logging -import re -from collections.abc import Mapping -from typing import Any - -from ..common.exception import PyAMLConfigException - -logger = logging.getLogger(__name__) - - -def substitute(obj: Any, arguments: Mapping[str, object]) -> Any: - """ - Recursively substitute named placeholders in configuration values. - - Parameters - ---------- - obj : object - Configuration value to process. Dictionaries and lists are traversed; - dictionary keys and non-string scalar values are left unchanged. - arguments : Mapping[str, object] - Parameter names mapped to replacement values, converted to strings. - - Returns - ------- - object - Configuration with substitutions applied and new dictionaries and lists. - - Notes - ----- - Replacements use ``{name}`` placeholders and follow argument insertion order. - Text inserted by one replacement can be modified by a later replacement. - """ - if isinstance(obj, dict): - return {key: substitute(value, arguments) for key, value in obj.items()} - if isinstance(obj, list): - return [substitute(value, arguments) for value in obj] - if isinstance(obj, str): - for name, value in arguments.items(): - obj = obj.replace("{" + name + "}", str(value)) - return obj - return obj - - -class TemplateManager: - """ - Store template definitions for one configuration manager or loading session. - - Definitions are isolated between instances and copied on registration and - generation so callers can modify their configurations independently. - - Methods - ------- - add(name, parameters, config) - Register a named template with ordered parameters. - generate(name, *args) - Substitute positional arguments into a copy of a template. - clear() - Remove all definitions from this registry. - """ - - def __init__(self) -> None: - """Initialize an empty template registry.""" - self.template_parameters: dict[str, list[str]] = {} - self.template_configs: dict[str, dict[str, Any]] = {} - - def add(self, name: str, parameters: list[str], config: dict[str, Any]) -> None: - """ - Register a template, copying its parameters and configuration. - - Parameters - ---------- - name : str - Template name, unique within this registry. - parameters : list[str] - Parameter names in the order expected by :meth:`generate`. - config : dict[str, Any] - Configuration body containing ``{parameter}`` placeholders. - - Raises - ------ - PyAMLConfigException - If the name is already registered. - """ - if name in self.template_configs: - raise PyAMLConfigException(f"Template '{name}' has already been registered.") - - self.template_parameters[name] = list(parameters) - self.template_configs[name] = copy.deepcopy(config) - - def generate(self, name: str, *args: object) -> dict[str, Any]: - """ - Generate a configuration by substituting positional arguments. - - Parameters - ---------- - name : str - Name of a registered template. - *args : object - Values corresponding to the template's ordered parameters. - Values are converted to strings during substitution. - - Returns - ------- - dict[str, Any] - Independent configuration with placeholders replaced. Resolver - expressions and file references are left for the loader to expand. - - Raises - ------ - KeyError - If the template name is not registered. - PyAMLConfigException - If the number of arguments does not match the parameters. - - Notes - ----- - Arguments containing brace-delimited placeholders produce a logging - warning because sequential substitution may modify their contents. - """ - number_of_parameters = len(self.template_parameters[name]) - number_of_arguments = len(args) - - if number_of_parameters != number_of_arguments: - raise PyAMLConfigException( - f"Invalid number of arguments ({args}: {number_of_arguments}) passed to template {name}." - f" Expected {number_of_parameters}." - ) - - # name the arguments by position - arguments_dict: dict[str, object] = {} - for arg_name, arg_value in zip(self.template_parameters[name], args, strict=True): - # check if {...} is included in any of the arguments, and a raise a warning if so. - if re.search(r"\{[^{}]+\}", str(arg_value)): - logger.warning( - f"Argument {arg_name!r} for template {name!r} contains a placeholder: {arg_value!r}. " - "Sequential replacement may substitute placeholders inside this argument.", - ) - arguments_dict[arg_name] = arg_value - - config = substitute(copy.deepcopy(self.template_configs[name]), arguments_dict) - - return config - - def clear(self) -> None: - """Remove all definitions from this registry without affecting other instances.""" - self.template_parameters.clear() - self.template_configs.clear() diff --git a/tests/configuration/test_templates.py b/tests/configuration/test_templates.py deleted file mode 100644 index 712040b7..00000000 --- a/tests/configuration/test_templates.py +++ /dev/null @@ -1,240 +0,0 @@ -import json - -import pytest -import yaml - -from pyaml import PyAMLException -from pyaml.accelerator import Accelerator -from pyaml.common.exception import PyAMLConfigException -from pyaml.configuration import ConfigurationManager -from pyaml.configuration.fileloader import FIELD_LOCATIONS_KEY, ROOT, load -from pyaml.configuration.template import TemplateManager - - -def _template_definition(prefix=""): - return { - "name": "device", - "parameters": ["name"], - "config": {"class": "pyaml.magnet.hcorrector.HCorrector", "name": prefix + "{name}"}, - } - - -def _write_config(path, config): - path.write_text(json.dumps(config) if path.suffix == ".json" else yaml.safe_dump(config, sort_keys=False)) - return path - - -@pytest.fixture -def template_config_root(tmp_path): - previous_root = ROOT.get() - ROOT.set(tmp_path) - try: - yield tmp_path - finally: - ROOT.set(previous_root) - - -def test_template_definitions_are_independent_of_inputs_and_results(): - registry = TemplateManager() - parameters = ["name"] - config = {"name": "{name}", "nested": [1]} - registry.add("T", parameters, config) - - parameters.clear() - config["nested"].append(2) - - generated = registry.generate("T", "Q1") - assert generated == {"name": "Q1", "nested": [1]} - - generated["nested"].append(3) - - assert registry.generate("T", "Q2") == {"name": "Q2", "nested": [1]} - - -def test_template_registry_rejects_duplicate_names(): - registry = TemplateManager() - registry.add("T", ["name"], {"name": "{name}"}) - - with pytest.raises(PyAMLConfigException, match="Template 'T'.*registered"): - registry.add("T", [], {}) - - -@pytest.mark.parametrize("suffix", [".yaml", ".json"]) -def test_standalone_loads_have_fresh_template_registries(tmp_path, suffix): - path = _write_config( - tmp_path / ("config" + suffix), - {"templates": [_template_definition()], "devices": ["${template:device,Q1}"]}, - ) - - first = load(str(path)) - second = load(str(path)) - - assert first["devices"][0]["name"] == "Q1" - assert second["devices"][0]["name"] == "Q1" - - use_only = _write_config(tmp_path / "use.yaml", {"devices": ["${template:device,Q1}"]}) - with pytest.raises(PyAMLException, match="Invalid template resolver call"): - load(str(use_only)) - - -def test_configuration_managers_have_independent_templates(tmp_path): - first_definitions = _write_config(tmp_path / "first.yaml", {"templates": [_template_definition("first-")]}) - second_definitions = _write_config(tmp_path / "second.json", {"templates": [_template_definition("second-")]}) - usage = _write_config(tmp_path / "usage.yaml", {"devices": ["${template:device,Q1}"]}) - first = ConfigurationManager() - second = ConfigurationManager() - - first.add(first_definitions) - second.add(second_definitions) - first.add(usage) - second.add(usage) - - assert first.keys("devices") == ["first-Q1"] - assert second.keys("devices") == ["second-Q1"] - - first.clear() - second.clear("devices") - second.add(usage) - - assert second.keys("devices") == ["second-Q1"] - - -def test_manager_category_clear_preserves_templates(tmp_path): - definitions = _write_config(tmp_path / "definitions.yaml", {"templates": [_template_definition()]}) - usage = _write_config(tmp_path / "usage.yaml", {"devices": ["${template:device,Q1}"]}) - manager = ConfigurationManager() - manager.add(definitions) - manager.add(usage) - - manager.clear("devices") - manager.add(usage) - - assert manager.keys("devices") == ["Q1"] - - -def test_manager_clear_resets_template_registration(tmp_path): - definitions = _write_config(tmp_path / "definitions.yaml", {"templates": [_template_definition()]}) - usage = _write_config(tmp_path / "usage.yaml", {"devices": ["${template:device,Q1}"]}) - manager = ConfigurationManager() - manager.add(definitions) - - manager.clear() - - with pytest.raises(PyAMLException, match="Invalid template resolver call"): - manager.add(usage) - - manager.add(definitions) - manager.add(usage) - - assert manager.keys("devices") == ["Q1"] - - -def test_included_files_and_nested_templates_share_registry(template_config_root, monkeypatch): - tmp_path = template_config_root - monkeypatch.setenv("PYAML_TEST_HOST", "localhost") - _write_config(tmp_path / "definitions.yaml", {"templates": [_template_definition()]}) - _write_config(tmp_path / "Q1.json", {"factor": 1.5}) - path = _write_config( - tmp_path / "parent.yaml", - { - "templates": [ - { - "name": "wrapper", - "parameters": ["name"], - "config": { - "device": "${template:device,{name}}", - "model": "{name}.json", - "host": "${env:PYAML_TEST_HOST}", - }, - } - ], - "definitions": "definitions.yaml", - "result": "${template:wrapper,Q1}", - }, - ) - - loaded = load(str(path), include_locations=True) - result = loaded["result"] - - assert result["device"]["name"] == "Q1" - assert result["model"] == {"factor": 1.5} - assert result["host"] == "localhost" - assert "templates" in loaded[FIELD_LOCATIONS_KEY] - assert "name" in result["device"][FIELD_LOCATIONS_KEY] - - -def test_explicit_registry_can_be_shared_between_standalone_loads(tmp_path): - registry = TemplateManager() - definitions = _write_config(tmp_path / "definitions.yaml", {"templates": [_template_definition()]}) - usage = _write_config(tmp_path / "usage.json", {"devices": ["${template:device,Q1}"]}) - - load(str(definitions), templates=registry) - result = load(str(usage), templates=registry) - - assert result["devices"][0]["name"] == "Q1" - - -def test_template_calls_distinguish_no_arguments_from_an_empty_argument(tmp_path): - path = _write_config( - tmp_path / "arguments.yaml", - { - "templates": [ - {"name": "constant", "parameters": [], "config": {"name": "Q1"}}, - _template_definition(), - ], - "constant": "${template:constant}", - "empty": "${template:device,}", - }, - ) - - result = load(str(path)) - - assert result["constant"]["name"] == "Q1" - assert result["empty"]["name"] == "" - - -def test_template_call_without_required_arguments_reports_count_error(tmp_path): - path = _write_config( - tmp_path / "missing_argument.yaml", - {"templates": [_template_definition()], "result": "${template:device}"}, - ) - - with pytest.raises(PyAMLConfigException, match=r"Invalid number of arguments.*Expected 1"): - load(str(path)) - - -def test_recursive_template_still_reports_configuration_error(tmp_path): - path = _write_config( - tmp_path / "recursive.yaml", - { - "templates": [{"name": "T", "parameters": ["name"], "config": {"child": "${template:T,{name}}"}}], - "result": "${template:T,Q1}", - }, - ) - - with pytest.raises(PyAMLException, match="Recursion limit reached while expanding template"): - load(str(path)) - - -def test_accelerator_load_does_not_clear_an_existing_managers_templates(template_config_root): - tmp_path = template_config_root - definitions = _write_config(tmp_path / "definitions.yaml", {"templates": [_template_definition()]}) - manager = ConfigurationManager() - manager.add(definitions) - accelerator = _write_config( - tmp_path / "accelerator.yaml", - { - "class": "pyaml.accelerator.Accelerator", - "facility": "test", - "machine": "sr", - "energy": 3e9, - "templates": [_template_definition("other-")], - }, - ) - usage = _write_config(tmp_path / "usage.yaml", {"devices": ["${template:device,Q1}"]}) - - for _ in range(2): - Accelerator.load(str(accelerator)) - manager.add(usage) - - assert manager.keys("devices") == ["Q1"]