From a0efe08b3ea075026f6a958f83b528d252b1138e Mon Sep 17 00:00:00 2001 From: kparasch Date: Fri, 16 Jan 2026 17:11:54 +0100 Subject: [PATCH 1/6] if orm file doesn't exist for orbit raise a warning during initialization, raise exception if you try to correct --- pyaml/tuning_tools/orbit.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pyaml/tuning_tools/orbit.py b/pyaml/tuning_tools/orbit.py index 20ea94dd..85199b68 100644 --- a/pyaml/tuning_tools/orbit.py +++ b/pyaml/tuning_tools/orbit.py @@ -1,6 +1,6 @@ import logging from pathlib import Path -from typing import TYPE_CHECKING, Literal, Optional +from typing import TYPE_CHECKING, Literal, Optional, Union try: from typing import Self # Python 3.11+ @@ -16,6 +16,8 @@ from ..arrays.magnet_array import MagnetArray from ..common.element import Element, ElementConfigModel from ..common.exception import PyAMLException +from ..configuration.factory import Factory +from ..configuration.fileloader import get_path, load from ..external.pySC.pySC import ResponseMatrix as pySC_ResponseMatrix from ..external.pySC.pySC.apps import orbit_correction from ..external.pySC_interface import pySCInterface @@ -34,21 +36,25 @@ class ConfigModel(ElementConfigModel): hcorr_array_name: str vcorr_array_name: str singular_values: int - response_matrix: Optional[ResponseMatrix] + response_matrix: Union[ResponseMatrix | str] class Orbit(Element): def __init__(self, cfg: ConfigModel): super().__init__(cfg.name) self._cfg = cfg - self.bpm_array_name = cfg.bpm_array_name self.hcorr_array_name = cfg.hcorr_array_name self.vcorr_array_name = cfg.vcorr_array_name self.singular_values = cfg.singular_values - self.response_matrix = pySC_ResponseMatrix.model_validate( - cfg.response_matrix._cfg.model_dump() - ) + + if type(cfg.response_matrix) is str: + logger.warning(f"{cfg.name} does not have a response_matrix!") + self.response_matrix = cfg.response_matrix + else: + self.response_matrix = pySC_ResponseMatrix.model_validate( + cfg.response_matrix._cfg.model_dump() + ) self._hcorr: MagnetArray = None self._vcorr: MagnetArray = None self._hvcorr: MagnetArray = None @@ -59,6 +65,9 @@ def correct( gain: float = 1.0, plane: Optional[Literal["H", "V"]] = None, ): + if self.response_matrix is str: + raise PyAMLException("f{cfg.name} does not have a response_matrix.") + interface = pySCInterface( element_holder=self._peer, bpm_array_name=self.bpm_array_name, @@ -130,3 +139,10 @@ def attach(self, peer: "ElementHolder") -> Self: obj = self.__class__(self._cfg) obj._peer = peer return obj + + def load_response_matrix(self, filename: str) -> None: + path = Path(filename) + config_dict = load(str(path.resolve())) + rm = Factory.depth_first_build(config_dict, ignore_external=False) + self.response_matrix = pySC_ResponseMatrix.model_validate(rm._cfg.model_dump()) + return None From fea7946f4821dcf886e8403d3bf5937a75ace3dc Mon Sep 17 00:00:00 2001 From: kparasch Date: Mon, 19 Jan 2026 09:44:13 +0100 Subject: [PATCH 2/6] make response_matrix optional and put a measure orm script --- examples/BESSY2_example/measure_ideal_ORM.py | 26 ++++++++++++++++++++ pyaml/tuning_tools/orbit.py | 8 +++--- 2 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 examples/BESSY2_example/measure_ideal_ORM.py diff --git a/examples/BESSY2_example/measure_ideal_ORM.py b/examples/BESSY2_example/measure_ideal_ORM.py new file mode 100644 index 00000000..8ea0ddb8 --- /dev/null +++ b/examples/BESSY2_example/measure_ideal_ORM.py @@ -0,0 +1,26 @@ +from pathlib import Path + +from pyaml.accelerator import Accelerator +from pyaml.tuning_tools.orbit_response_matrix import ConfigModel as ORM_ConfigModel +from pyaml.tuning_tools.orbit_response_matrix import OrbitResponseMatrix + +parent_folder = Path(__file__).parent +config_path = parent_folder.joinpath("BESSY2Orbit.yaml").resolve() + +sr = Accelerator.load(config_path) +element_holder = sr.design + + +orbit = element_holder.orbit +orm = OrbitResponseMatrix( + cfg=ORM_ConfigModel( + bpm_array_name=orbit.bpm_array_name, + hcorr_array_name=orbit.hcorr_array_name, + vcorr_array_name=orbit.vcorr_array_name, + corrector_delta=1e-6, + ), + element_holder=element_holder, +) + +orm.measure() +orm.save(parent_folder / Path("ideal_orm.json")) diff --git a/pyaml/tuning_tools/orbit.py b/pyaml/tuning_tools/orbit.py index 85199b68..6e96508f 100644 --- a/pyaml/tuning_tools/orbit.py +++ b/pyaml/tuning_tools/orbit.py @@ -36,7 +36,7 @@ class ConfigModel(ElementConfigModel): hcorr_array_name: str vcorr_array_name: str singular_values: int - response_matrix: Union[ResponseMatrix | str] + response_matrix: Optional[Union[ResponseMatrix, str]] = None class Orbit(Element): @@ -48,7 +48,7 @@ def __init__(self, cfg: ConfigModel): self.vcorr_array_name = cfg.vcorr_array_name self.singular_values = cfg.singular_values - if type(cfg.response_matrix) is str: + if type(cfg.response_matrix) is str or cfg.response_matrix is None: logger.warning(f"{cfg.name} does not have a response_matrix!") self.response_matrix = cfg.response_matrix else: @@ -65,8 +65,8 @@ def correct( gain: float = 1.0, plane: Optional[Literal["H", "V"]] = None, ): - if self.response_matrix is str: - raise PyAMLException("f{cfg.name} does not have a response_matrix.") + if type(self.response_matrix) is str or self.response_matrix is None: + raise PyAMLException(f"{self.get_name()} does not have a response_matrix.") interface = pySCInterface( element_holder=self._peer, From f73755818c4bd84e5f0f0a2774e8f48ce5c57a14 Mon Sep 17 00:00:00 2001 From: kparasch Date: Mon, 19 Jan 2026 14:03:58 +0100 Subject: [PATCH 3/6] remove script --- examples/BESSY2_example/measure_ideal_ORM.py | 26 -------------------- 1 file changed, 26 deletions(-) delete mode 100644 examples/BESSY2_example/measure_ideal_ORM.py diff --git a/examples/BESSY2_example/measure_ideal_ORM.py b/examples/BESSY2_example/measure_ideal_ORM.py deleted file mode 100644 index 8ea0ddb8..00000000 --- a/examples/BESSY2_example/measure_ideal_ORM.py +++ /dev/null @@ -1,26 +0,0 @@ -from pathlib import Path - -from pyaml.accelerator import Accelerator -from pyaml.tuning_tools.orbit_response_matrix import ConfigModel as ORM_ConfigModel -from pyaml.tuning_tools.orbit_response_matrix import OrbitResponseMatrix - -parent_folder = Path(__file__).parent -config_path = parent_folder.joinpath("BESSY2Orbit.yaml").resolve() - -sr = Accelerator.load(config_path) -element_holder = sr.design - - -orbit = element_holder.orbit -orm = OrbitResponseMatrix( - cfg=ORM_ConfigModel( - bpm_array_name=orbit.bpm_array_name, - hcorr_array_name=orbit.hcorr_array_name, - vcorr_array_name=orbit.vcorr_array_name, - corrector_delta=1e-6, - ), - element_holder=element_holder, -) - -orm.measure() -orm.save(parent_folder / Path("ideal_orm.json")) From d17beea10e4ea5bfa5b9d63ef8aa6b36cd57151d Mon Sep 17 00:00:00 2001 From: kparasch Date: Mon, 19 Jan 2026 10:37:38 +0100 Subject: [PATCH 4/6] introduce prefix 'file:' that is ignored in the factory when loading configuration. --- pyaml/configuration/fileloader.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyaml/configuration/fileloader.py b/pyaml/configuration/fileloader.py index 085af3cd..97804c0e 100644 --- a/pyaml/configuration/fileloader.py +++ b/pyaml/configuration/fileloader.py @@ -23,7 +23,7 @@ logger = logging.getLogger(__name__) accepted_suffixes = [".yaml", ".yml", ".json"] - +not_expanded_prefixes = ["file:"] ROOT = {"path": Path.cwd().resolve()} @@ -83,8 +83,10 @@ def load( # Expand condition def hasToExpand(value): - return isinstance(value, str) and any( - value.endswith(suffix) for suffix in accepted_suffixes + return ( + isinstance(value, str) + and any(value.endswith(suffix) for suffix in accepted_suffixes) + and not any(value.startswith(prefix) for prefix in not_expanded_prefixes) ) From 86408c93fc31255fc2659cfc7a2f324b13c728cf Mon Sep 17 00:00:00 2001 From: kparasch Date: Mon, 19 Jan 2026 13:11:28 +0100 Subject: [PATCH 5/6] better 'file:' prefix handling --- pyaml/configuration/fileloader.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/pyaml/configuration/fileloader.py b/pyaml/configuration/fileloader.py index 97804c0e..6df60c1d 100644 --- a/pyaml/configuration/fileloader.py +++ b/pyaml/configuration/fileloader.py @@ -23,7 +23,7 @@ logger = logging.getLogger(__name__) accepted_suffixes = [".yaml", ".yml", ".json"] -not_expanded_prefixes = ["file:"] +FILE_PREFIX = "file:" ROOT = {"path": Path.cwd().resolve()} @@ -82,11 +82,9 @@ def load( # Expand condition -def hasToExpand(value): - return ( - isinstance(value, str) - and any(value.endswith(suffix) for suffix in accepted_suffixes) - and not any(value.startswith(prefix) for prefix in not_expanded_prefixes) +def hasToLoad(value): + return isinstance(value, str) and any( + value.endswith(suffix) for suffix in accepted_suffixes ) @@ -107,8 +105,13 @@ def __init__(self, filename: str, parent_path_stack: list[Path]): def expand_dict(self, d: dict): for key, value in d.items(): try: - if hasToExpand(value): - d[key] = load(value, self.files_stack, self.use_fast_loader) + if hasToLoad(value): + if value.startswith(FILE_PREFIX): + # remove prefix + stripped_value = value[len(FILE_PREFIX) :] + d[key] = str(get_root_folder() / Path(stripped_value)) + else: + d[key] = load(value, self.files_stack, self.use_fast_loader) else: self.expand(value) except PyAMLConfigCyclingException as pyaml_ex: @@ -129,7 +132,7 @@ def expand_dict(self, d: dict): # Recursively expand a list def expand_list(self, l: list): for idx, value in enumerate(l): - if hasToExpand(value): + if hasToLoad(value): l[idx] = load(value, self.files_stack) else: self.expand(value) From 70825b957547bdd79f9cb37f8ae3b891920ad8fc Mon Sep 17 00:00:00 2001 From: kparasch Date: Mon, 19 Jan 2026 13:48:13 +0100 Subject: [PATCH 6/6] change config for ebsorbit --- pyaml/tuning_tools/orbit.py | 18 ++++++++++++------ tests/config/EBSOrbit.yaml | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pyaml/tuning_tools/orbit.py b/pyaml/tuning_tools/orbit.py index 6e96508f..8c8f8785 100644 --- a/pyaml/tuning_tools/orbit.py +++ b/pyaml/tuning_tools/orbit.py @@ -8,7 +8,7 @@ from typing_extensions import Self # Python 3.10 and earlier -from pydantic import BaseModel, ConfigDict +from pydantic import ConfigDict if TYPE_CHECKING: from ..common.element_holder import ElementHolder @@ -36,7 +36,7 @@ class ConfigModel(ElementConfigModel): hcorr_array_name: str vcorr_array_name: str singular_values: int - response_matrix: Optional[Union[ResponseMatrix, str]] = None + response_matrix: Union[str, ResponseMatrix] class Orbit(Element): @@ -48,13 +48,19 @@ def __init__(self, cfg: ConfigModel): self.vcorr_array_name = cfg.vcorr_array_name self.singular_values = cfg.singular_values - if type(cfg.response_matrix) is str or cfg.response_matrix is None: - logger.warning(f"{cfg.name} does not have a response_matrix!") - self.response_matrix = cfg.response_matrix + if type(cfg.response_matrix) is str: + response_matrix_filename = cfg.response_matrix + # assigns self.response_matrix + if Path(response_matrix_filename).exists(): + self.load_response_matrix(response_matrix_filename) + else: + logger.warning(f"{response_matrix_filename} does not exist.") + self.response_matrix = None else: self.response_matrix = pySC_ResponseMatrix.model_validate( cfg.response_matrix._cfg.model_dump() ) + self._hcorr: MagnetArray = None self._vcorr: MagnetArray = None self._hvcorr: MagnetArray = None @@ -65,7 +71,7 @@ def correct( gain: float = 1.0, plane: Optional[Literal["H", "V"]] = None, ): - if type(self.response_matrix) is str or self.response_matrix is None: + if self.response_matrix is None: raise PyAMLException(f"{self.get_name()} does not have a response_matrix.") interface = pySCInterface( diff --git a/tests/config/EBSOrbit.yaml b/tests/config/EBSOrbit.yaml index 10d95ba6..2699a112 100644 --- a/tests/config/EBSOrbit.yaml +++ b/tests/config/EBSOrbit.yaml @@ -934,7 +934,7 @@ devices: vcorr_array_name: VCorr name: DEFAULT_ORBIT_CORRECTION singular_values: 162 - response_matrix: ideal_orm_disp.json + response_matrix: file:ideal_orm_disp.json - type: pyaml.rf.rf_plant name: RF masterclock: