From 085fc2c23daec409d01a282f262411177635d977 Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Wed, 17 Dec 2025 09:58:00 -0800 Subject: [PATCH 1/5] Add test to validate examples in the main PALS repository --- .github/workflows/tests.yml | 16 +++++++++++++++- examples/test_external_examples.py | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 examples/test_external_examples.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e4d8a66..a4887be 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,6 +30,20 @@ jobs: - name: Test run: | pytest tests -v - - name: Examples + - name: Examples (internal) run: | python examples/fodo.py + - name: Examples (external) + run: | + # Copy examples directory from the main PALS repository + cd examples + git clone --no-checkout https://github.com/pals-project/pals.git pals_temp + cd pals_temp + git sparse-checkout init + git sparse-checkout set examples/ + git checkout main + # Test all external examples + cd - + for file in pals_temp/examples/*.pals.yaml; do + python test_external_examples.py --path "${file}" + done \ No newline at end of file diff --git a/examples/test_external_examples.py b/examples/test_external_examples.py new file mode 100644 index 0000000..d61bd03 --- /dev/null +++ b/examples/test_external_examples.py @@ -0,0 +1,26 @@ +import argparse +import yaml + +from pals import BeamLine + + +def main(): + # Parse command-line arguments + parser = argparse.ArgumentParser() + parser.add_argument( + "--path", + required=True, + help="Path to the example file", + ) + args = parser.parse_args() + example_file = args.path + # Read YAML data from file + with open(example_file, "r") as file: + data = yaml.safe_load(file) + # Parse and validate YAML data + print(f"Parsing data from {example_file}...") + BeamLine(**data) + + +if __name__ == "__main__": + main() From 22c0f45f1d828643fef9d3d859969cbb32362462 Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Mon, 9 Feb 2026 15:53:03 -0800 Subject: [PATCH 2/5] Use BeamLine.from_file to read from file --- examples/test_external_examples.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/test_external_examples.py b/examples/test_external_examples.py index d61bd03..828c183 100644 --- a/examples/test_external_examples.py +++ b/examples/test_external_examples.py @@ -1,5 +1,4 @@ import argparse -import yaml from pals import BeamLine @@ -14,12 +13,8 @@ def main(): ) args = parser.parse_args() example_file = args.path - # Read YAML data from file - with open(example_file, "r") as file: - data = yaml.safe_load(file) - # Parse and validate YAML data - print(f"Parsing data from {example_file}...") - BeamLine(**data) + # Parse and validate YAML data from file + BeamLine.from_file(example_file) if __name__ == "__main__": From 82a2486cff27b280bc228c5d29fda41a6ab8681e Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Tue, 10 Feb 2026 13:34:13 -0800 Subject: [PATCH 3/5] Start draft of new Lattice class - Copy implementation from BeamLine to Lattice - Move methods from_file, to_file from BeamLine to Lattice - Use new class in external examples test script --- examples/test_external_examples.py | 4 +-- src/pals/kinds/BeamLine.py | 12 --------- src/pals/kinds/Lattice.py | 39 ++++++++++++++++++++++++++++++ src/pals/kinds/all_elements.py | 1 + 4 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 src/pals/kinds/Lattice.py diff --git a/examples/test_external_examples.py b/examples/test_external_examples.py index 828c183..3f0a513 100644 --- a/examples/test_external_examples.py +++ b/examples/test_external_examples.py @@ -1,6 +1,6 @@ import argparse -from pals import BeamLine +from pals import Lattice def main(): @@ -14,7 +14,7 @@ def main(): args = parser.parse_args() example_file = args.path # Parse and validate YAML data from file - BeamLine.from_file(example_file) + Lattice.from_file(example_file) if __name__ == "__main__": diff --git a/src/pals/kinds/BeamLine.py b/src/pals/kinds/BeamLine.py index f7dd881..2df4fca 100644 --- a/src/pals/kinds/BeamLine.py +++ b/src/pals/kinds/BeamLine.py @@ -3,7 +3,6 @@ from .all_elements import get_all_elements_as_annotation from .mixin import BaseElement -from ..functions import load_file_to_dict, store_dict_to_file class BeamLine(BaseElement): @@ -26,14 +25,3 @@ def model_dump(self, *args, **kwargs): from pals.kinds.mixin.all_element_mixin import dump_element_list return dump_element_list(self, "line", *args, **kwargs) - - @staticmethod - def from_file(filename: str) -> "BeamLine": - """Load a BeamLine from a text file""" - pals_dict = load_file_to_dict(filename) - return BeamLine(**pals_dict) - - def to_file(self, filename: str): - """Save a BeamLine to a text file""" - pals_dict = self.model_dump() - store_dict_to_file(filename, pals_dict) diff --git a/src/pals/kinds/Lattice.py b/src/pals/kinds/Lattice.py new file mode 100644 index 0000000..270f0c8 --- /dev/null +++ b/src/pals/kinds/Lattice.py @@ -0,0 +1,39 @@ +from pydantic import model_validator +from typing import List, Literal + +from .all_elements import get_all_elements_as_annotation +from .mixin import BaseElement +from ..functions import load_file_to_dict, store_dict_to_file + + +class Lattice(BaseElement): + """A line of elements and/or other lines""" + + kind: Literal["Lattice"] = "Lattice" + + line: List[get_all_elements_as_annotation()] + + @model_validator(mode="before") + @classmethod + def unpack_json_structure(cls, data): + """Deserialize the JSON/YAML/...-like dict for Lattice elements""" + from pals.kinds.mixin.all_element_mixin import unpack_element_list_structure + + return unpack_element_list_structure(data, "line", "line") + + def model_dump(self, *args, **kwargs): + """Custom model dump for Lattice to handle element list formatting""" + from pals.kinds.mixin.all_element_mixin import dump_element_list + + return dump_element_list(self, "line", *args, **kwargs) + + @staticmethod + def from_file(filename: str) -> "Lattice": + """Load a Lattice from a text file""" + pals_dict = load_file_to_dict(filename) + return Lattice(**pals_dict) + + def to_file(self, filename: str): + """Save a Lattice to a text file""" + pals_dict = self.model_dump() + store_dict_to_file(filename, pals_dict) diff --git a/src/pals/kinds/all_elements.py b/src/pals/kinds/all_elements.py index 580388e..f2d0d70 100644 --- a/src/pals/kinds/all_elements.py +++ b/src/pals/kinds/all_elements.py @@ -43,6 +43,7 @@ def get_all_element_types(extra_types: tuple = None): """Return a tuple of all element types that can be used in BeamLine or UnionEle.""" element_types = ( + "Lattice", # Forward reference to handle circular import "BeamLine", # Forward reference to handle circular import "UnionEle", # Forward reference to handle circular import ACKicker, From 89a5e531294f3a94ab99e17658d0feaf02a4f1dd Mon Sep 17 00:00:00 2001 From: Edoardo Zoni Date: Tue, 10 Feb 2026 13:43:31 -0800 Subject: [PATCH 4/5] Reexport, rebuild new Lattice class --- src/pals/kinds/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pals/kinds/__init__.py b/src/pals/kinds/__init__.py index d64c12c..36471e8 100644 --- a/src/pals/kinds/__init__.py +++ b/src/pals/kinds/__init__.py @@ -5,6 +5,7 @@ from .ACKicker import ACKicker # noqa: F401 from .BeamBeam import BeamBeam # noqa: F401 from .BeamLine import BeamLine # noqa: F401 +from .Lattice import Lattice # noqa: F401 from .BeginningEle import BeginningEle # noqa: F401 from .Converter import Converter # noqa: F401 from .CrabCavity import CrabCavity # noqa: F401 @@ -39,3 +40,4 @@ # Rebuild pydantic models that depend on other classes UnionEle.model_rebuild() BeamLine.model_rebuild() +Lattice.model_rebuild() From d45ba9d1317d009145bcffc75bc8ed6cce270f4e Mon Sep 17 00:00:00 2001 From: Edoardo Zoni <59625522+EZoni@users.noreply.github.com> Date: Fri, 13 Feb 2026 10:09:02 -0800 Subject: [PATCH 5/5] Replace `line` with `branches`, list of `BeamLine`s only --- src/pals/kinds/Lattice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pals/kinds/Lattice.py b/src/pals/kinds/Lattice.py index 270f0c8..c2f204f 100644 --- a/src/pals/kinds/Lattice.py +++ b/src/pals/kinds/Lattice.py @@ -11,7 +11,7 @@ class Lattice(BaseElement): kind: Literal["Lattice"] = "Lattice" - line: List[get_all_elements_as_annotation()] + branches: List[Annotated[Union[BeamLine], Field(discriminator="kind")]] @model_validator(mode="before") @classmethod