-
Notifications
You must be signed in to change notification settings - Fork 3
30 modular linking between pyat and pyaml elements #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
JeanLucPons
merged 5 commits into
main
from
30-modular-linking-between-pyat-and-pyaml-elements
Oct 15, 2025
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
f9b5d3d
First implementation for linkers.
gupichon-soleil aea2a46
New tests for 2 default linkers
gupichon-soleil effe0b5
Bug correction for configuration files and according new test
gupichon-soleil 0174c3a
Small test improvement
gupichon-soleil c58ec75
Removing famname_linker.py: It's relaced by the attribute_linker with…
gupichon-soleil File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import at | ||
| from pydantic import ConfigDict | ||
|
|
||
| from pyaml.lattice.element import Element | ||
| from pyaml.lattice.lattice_elements_linker import LinkerIdentifier, LinkerConfigModel, LatticeElementsLinker | ||
|
|
||
| PYAMLCLASS = "PyAtAttributeElementsLinker" | ||
|
|
||
|
|
||
| class ConfigModel(LinkerConfigModel): | ||
| """Base configuration model for linker definitions. | ||
|
|
||
| This class defines the configuration structure used to instantiate | ||
| a specific linking strategy. Each concrete implementation of a | ||
| `LatticeElementsLinker` may define its own subclass extending this model | ||
| to include additional configuration parameters. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| model_config : ConfigDict | ||
| Pydantic configuration allowing arbitrary field types and forbidding | ||
| unexpected extra keys. | ||
| """ | ||
| model_config = ConfigDict(arbitrary_types_allowed=True,extra="forbid") | ||
| attribute_name: str | ||
|
|
||
|
|
||
| class PyAtAttributeIdentifier(LinkerIdentifier): | ||
| """Abstract base class for identifiers used to match PyAML and PyAT elements. | ||
|
|
||
| The identifier acts as an intermediate representation between the PyAML | ||
| configuration and the PyAT lattice. Its exact structure depends on the | ||
| linking strategy (e.g., family name, element index, or user-defined tag). | ||
|
|
||
| Subclasses should define the fields and logic necessary to represent | ||
| a unique reference to one or more PyAT elements. | ||
| """ | ||
|
|
||
| def __init__(self, attribute_name:str, identifier): | ||
| self.attribute_name = attribute_name | ||
| self.identifier = identifier | ||
|
|
||
| def __repr__(self): | ||
| return f"{self.attribute_name}={self.identifier}" | ||
|
|
||
|
|
||
| class PyAtAttributeElementsLinker(LatticeElementsLinker): | ||
| """Abstract base class defining the interface for PyAT–PyAML element linking. | ||
|
|
||
| Implementations of this class define how PyAML elements are matched | ||
| to PyAT elements based on a given linking strategy (e.g., by family name, | ||
| by index, or by a custom attribute). | ||
|
|
||
| Parameters | ||
| ---------- | ||
| config_model : ConfigModel | ||
| The configuration model for the linking strategy. | ||
| """ | ||
|
|
||
| def __init__(self, config_model:ConfigModel): | ||
| super().__init__(config_model) | ||
|
|
||
| def get_element_identifier(self, element: Element) -> LinkerIdentifier: | ||
| return PyAtAttributeIdentifier(self.linker_config_model.attribute_name, element.name) | ||
|
|
||
| def _test_at_element(self, identifier: PyAtAttributeIdentifier, element: at.Element) -> bool: | ||
| attr_value = getattr(element, identifier.attribute_name, None) | ||
| return attr_value == identifier.identifier |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| from abc import ABCMeta, abstractmethod | ||
| from typing import Iterable | ||
|
|
||
| import at | ||
| from at import Lattice | ||
| from pydantic import BaseModel, ConfigDict | ||
|
|
||
| from pyaml import PyAMLException | ||
| from pyaml.lattice.element import Element | ||
|
|
||
|
|
||
| class LinkerConfigModel(BaseModel): | ||
| """Base configuration model for linker definitions. | ||
|
|
||
| This class defines the configuration structure used to instantiate | ||
| a specific linking strategy. Each concrete implementation of a | ||
| `LatticeElementsLinker` may define its own subclass extending this model | ||
| to include additional configuration parameters. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| model_config : ConfigDict | ||
| Pydantic configuration allowing arbitrary field types and forbidding | ||
| unexpected extra keys. | ||
| """ | ||
| model_config = ConfigDict(arbitrary_types_allowed=True,extra="forbid") | ||
|
|
||
|
|
||
| class LinkerIdentifier(metaclass=ABCMeta): | ||
| """Abstract base class for identifiers used to match PyAML and PyAT elements. | ||
|
|
||
| The identifier acts as an intermediate representation between the PyAML | ||
| configuration and the PyAT lattice. Its exact structure depends on the | ||
| linking strategy (e.g., family name, element index, or user-defined tag). | ||
|
|
||
| Subclasses should define the fields and logic necessary to represent | ||
| a unique reference to one or more PyAT elements. | ||
| """ | ||
| pass | ||
|
|
||
|
|
||
| class LatticeElementsLinker(metaclass=ABCMeta): | ||
| """Abstract base class defining the interface for PyAT–PyAML element linking. | ||
|
|
||
| Implementations of this class define how PyAML elements are matched | ||
| to PyAT elements based on a given linking strategy (e.g., by family name, | ||
| by index, or by a custom attribute). | ||
|
|
||
| Parameters | ||
| ---------- | ||
| linker_config_model : LinkerConfigModel | ||
| The configuration model for the linking strategy. | ||
|
|
||
| Attributes | ||
| ---------- | ||
| lattice : at.Lattice | ||
| Reference to the PyAT lattice handled by this linker. | ||
| """ | ||
|
|
||
| def __init__(self, linker_config_model:LinkerConfigModel): | ||
| self.linker_config_model = linker_config_model | ||
| self.lattice:Lattice = None | ||
|
|
||
| def set_lattice(self, lattice:Lattice): | ||
| self.lattice = lattice | ||
|
|
||
| @abstractmethod | ||
| def _test_at_element(self, identifier: LinkerIdentifier, element:at.Element) -> bool: | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def get_element_identifier(self, element:Element) -> LinkerIdentifier: | ||
| pass | ||
|
|
||
| def _iter_matches(self, identifier: LinkerIdentifier) -> Iterable[at.Element]: | ||
| """Yield all elements in the lattice whose matches the identifier.""" | ||
| for elem in self.lattice: | ||
| if self._test_at_element(identifier, elem): | ||
| yield elem | ||
|
|
||
| def get_at_elements(self,element_id:LinkerIdentifier|list[LinkerIdentifier]) -> list[at.Element]: | ||
| """Return a list of PyAT elements matching the given identifiers. | ||
|
|
||
| This method should resolve one or multiple PyAML identifiers | ||
| into their corresponding PyAT elements according to the specific | ||
| linking strategy implemented. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| element_id : LinkerIdentifier or list of LinkerIdentifier | ||
| One or several identifiers describing which PyAT elements | ||
| to retrieve. | ||
|
|
||
| Returns | ||
| ------- | ||
| list of at.Element | ||
| The list of matching PyAT elements found in the lattice. | ||
|
|
||
| Raises | ||
| ------ | ||
| PyAMLException | ||
| If no element matches the given identifier(s). | ||
| """ | ||
| if isinstance(element_id, LinkerIdentifier): | ||
| identifiers = [element_id] | ||
| else: | ||
| identifiers = element_id | ||
|
|
||
| results: list[at.Element] = [] | ||
| for ident in identifiers: | ||
| results.extend(self._iter_matches(ident)) | ||
|
|
||
| if not results: | ||
| raise PyAMLException( | ||
| f"No PyAT elements found for identifier(s): " | ||
| f"{', '.join(i.__repr__() for i in identifiers)}" | ||
| ) | ||
| return results | ||
|
|
||
| def get_at_element(self, element_id:LinkerIdentifier) -> at.Element: | ||
| """Return a single PyAT element matching the given identifier. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| element_id : LinkerIdentifier | ||
| Identifier describing the PyAT element to retrieve. | ||
|
|
||
| Returns | ||
| ------- | ||
| at.Element | ||
| The PyAT element matching the identifier. | ||
|
|
||
| Raises | ||
| ------ | ||
| PyAMLException | ||
| If no element matches the identifier. | ||
| """ | ||
| for elem in self._iter_matches(element_id): | ||
| return elem | ||
| raise PyAMLException(f"No PyAT element found for FamName: {element_id.__repr__()}") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| type: pyaml.pyaml | ||
| instruments: | ||
| - type: pyaml.instrument | ||
| name: sr | ||
| energy: 6e9 | ||
| simulators: | ||
| - type: pyaml.lattice.simulator | ||
| lattice: sr/lattices/ebs.mat | ||
| name: design | ||
| linker: | ||
| type: pyaml.lattice.attribute_linker | ||
| attribute_name: FamName # equivalent to the default linker | ||
| data_folder: /data/store | ||
| arrays: | ||
| - type: pyaml.arrays.hcorrector | ||
| name: HCORR | ||
| elements: | ||
| - SH1A-C01-H | ||
| - SH1A-C02-H | ||
| - type: pyaml.arrays.vcorrector | ||
| name: VCORR | ||
| elements: | ||
| - SH1A-C01-V | ||
| - SH1A-C02-V | ||
| devices: | ||
| - sr/quadrupoles/QF1AC01.yaml | ||
| - sr/correctors/SH1AC01.yaml | ||
| - sr/correctors/SH1AC02.yaml |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename get_at_element() to get_first_at_element() ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of this method is to ensure unicity.
If you want the first of a group, you can just call
get_at_elements()[0]This method may be useless but it will depends of future developpements.