diff --git a/pyaml/accelerator.py b/pyaml/accelerator.py index 6ae099f8..924242d5 100644 --- a/pyaml/accelerator.py +++ b/pyaml/accelerator.py @@ -4,10 +4,8 @@ import warnings -from pydantic import BaseModel, ConfigDict, Field - from .arrays.array import ArrayConfig -from .common.element import Element +from .common.element import Element, __pyaml_repr__ from .common.element_holder import ElementHolder from .common.exception import PyAMLConfigException from .configuration import ConfigurationManager, UnsupportedConfigurationRootError @@ -21,102 +19,119 @@ PYAMLCLASS = "Accelerator" -class ConfigModel(BaseModel): +class Accelerator: """ - Configuration model for Accelerator + Top-level accelerator object. + + An Accelerator represents a complete accelerator model, including its + machine-wide properties, devices, arrays, control systems, and simulators. + It serves as the main entry point for loading, constructing, and interacting + with an accelerator configuration. Parameters ---------- facility : str - Facility name + Facility name. machine : str - Accelerator name + Accelerator name. energy : float - Accelerator nominal energy. For ramped machine, - this value can be dynamically set + Nominal accelerator energy. alphac : float, optional - Moment compaction factor. - harmonic_number: int, optional - Number of bucket + Momentum compaction factor. + harmonic_number : int, optional + Harmonic number. controls : list[ControlSystem], optional - List of control system used. An accelerator - can access several control systems + Control systems associated with the accelerator. simulators : list[Simulator], optional - Simulator list - data_folder : str - Data folder + Simulators associated with the accelerator. arrays : list[ArrayConfig], optional - Element family - description : str , optional - Acceleration description - devices : list[.common.element.Element] - Element list + Array configurations. + devices : list[Element], optional + Accelerator devices. + data_folder : str, optional + Path to the accelerator data directory. + description : str, optional + Human-readable description of the accelerator. + + Notes + ----- + Control systems and simulators are registered by name and are exposed as + attributes of the accelerator instance. The control system named ``"live"`` + and the simulator named ``"design"`` are additionally available through the + :attr:`live` and :attr:`design` properties. """ - model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") - - facility: str - machine: str - energy: float - alphac: float | None = None - harmonic_number: int | None = None - controls: list[ControlSystem] = None - simulators: list[Simulator] = None - data_folder: str - description: str | None = None - arrays: list[ArrayConfig] = Field(default=None, repr=False) - devices: list[Element] = Field(repr=False) + def __init__( + self, + facility: str, + machine: str, + energy: float, + alphac: float | None = None, + harmonic_number: int | None = None, + controls: list[ControlSystem] | None = None, + simulators: list[Simulator] | None = None, + arrays: list[ArrayConfig] | None = None, + devices: list[Element] | None = None, + data_folder: str | None = None, + description: str | None = None, + ): + self.facility = facility + self.machine = machine + self._data_folder = data_folder + self.description = description + + self._energy = float(energy) if energy is not None else None + self._alphac = float(alphac) if alphac is not None else None + self._harmonic_number = int(harmonic_number) if harmonic_number is not None else None + + self._arrays = arrays + self._devices = devices - -class Accelerator(object): - """PyAML top level class""" - - def __init__(self, cfg: ConfigModel): - self._cfg = cfg self.__design = None self.__live = None + self._controls: dict[str, ElementHolder] = {} self._simulators: dict[str, ElementHolder] = {} - if cfg.controls is not None: - for c in cfg.controls: + if controls is not None: + for c in controls: if c.name() == "live": self.__live = c else: # Add as dynamic attribute setattr(self, c.name(), c) - c.fill_device(cfg.devices) + c.fill_device(self._devices) c._peer = self self._controls[c.name()] = c - if cfg.simulators is not None: - for s in cfg.simulators: + if simulators is not None: + for s in simulators: if s.name() == "design": self.__design = s else: # Add as dynamic attribute setattr(self, s.name(), s) - s.fill_device(cfg.devices) + s.fill_device(self._devices) s._peer = self self._simulators[s.name()] = s - if cfg.arrays is not None: - for a in cfg.arrays: - if cfg.simulators is not None: - for s in cfg.simulators: + if arrays is not None: + for a in self._arrays: + if self._simulators is not None: + for s in self._simulators.values(): a.fill_array(s) - if cfg.controls is not None: - for c in cfg.controls: + if self._controls is not None: + for c in self._controls.values(): a.fill_array(c) - if cfg.energy is not None: - self.set_energy(cfg.energy) + if self._energy is not None: + self.set_energy(self._energy) - if cfg.alphac is not None: - self.set_mcf(cfg.alphac) + if self._alphac is not None: + self.set_mcf(self._alphac) - if cfg.harmonic_number is not None: - self.set_harmonic_number(cfg.harmonic_number) + if self._harmonic_number is not None: + self.set_harmonic_number(self._harmonic_number) self._yellow_pages = YellowPages(self) @@ -124,12 +139,12 @@ def __init__(self, cfg: ConfigModel): def _set_properties(self, method: str, value): # Sets global property - if self._cfg.simulators is not None: - for s in self._cfg.simulators: + if self._simulators is not None: + for s in self._simulators.values(): m = getattr(s, method) m(value) - if self._cfg.controls is not None: - for c in self._cfg.controls: + if self._controls is not None: + for c in self._controls.values(): m = getattr(c, method) m(value) @@ -183,31 +198,31 @@ def add_device(self, config: dict, ignore_external=False): "Invalid device type, Element or sub classes of Element expected " + f"but got {dev.__class__.__name__}" ) - self._cfg.devices.append(dev) - if self._cfg.controls is not None: - for c in self._cfg.controls: + self._devices.append(dev) + if self._controls is not None: + for c in self._controls: c.fill_device([dev]) - if self._cfg.simulators is not None: - for s in self._cfg.simulators: + if self._simulators is not None: + for s in self._simulators: s.fill_device([dev]) def post_init(self): """ Method triggered after all initialisations are done """ - if self._cfg.simulators is not None: - for s in self._cfg.simulators: + if self._simulators is not None: + for s in self._simulators.values(): s.post_init() - if self._cfg.controls is not None: - for c in self._cfg.controls: + if self._controls is not None: + for c in self._controls.values(): c.post_init() def get_description(self) -> str: """ Returns the description of the accelerator """ - return self._cfg.description + return self.description @property def live(self) -> ControlSystem: @@ -253,7 +268,7 @@ def modes(self) -> dict[str, "ElementHolder"]: return modes def __repr__(self): - return repr(self._cfg).replace("ConfigModel", self.__class__.__name__) + return __pyaml_repr__(self) @staticmethod def from_dict(config_dict: dict, ignore_external: bool = False, validate: bool = False) -> "Accelerator": diff --git a/pyaml/configuration/manager.py b/pyaml/configuration/manager.py index 4d3bd501..165236f1 100644 --- a/pyaml/configuration/manager.py +++ b/pyaml/configuration/manager.py @@ -15,6 +15,7 @@ import copy import fnmatch +import inspect import os import re from pathlib import Path @@ -81,28 +82,37 @@ class ConfigurationManager: @classmethod def root_fields(cls) -> tuple[str, ...]: r""" - Return the ordered root fields supported by the accelerator model. + Return the ordered root fields supported by the accelerator configuration. - The field order is derived from - :class:`~pyaml.accelerator.ConfigModel`. + The field order is derived from :meth:Accelerator.__init__, excluding + internal or categorized fields such as control and simulator collections. Returns - ------- + tuple[str, ...] + The root field names in configuration order, prefixed with "type". Examples - -------- .. code-block:: python - >>> ConfigurationManager.root_fields() - """ - from ..accelerator import ConfigModel as AcceleratorConfigModel + >>> ConfigurationManager.root_fields() - config_fields = tuple( - field_name for field_name in AcceleratorConfigModel.model_fields if field_name not in cls.NAMED_CATEGORIES + """ + params = inspect.signature(cls.__init__).parameters + + fields = tuple( + name + for name, param in params.items() + if name != "self" + and name not in cls.NAMED_CATEGORIES + and param.kind + in ( + inspect.Parameter.POSITIONAL_OR_KEYWORD, + inspect.Parameter.KEYWORD_ONLY, + ) ) - return ("type", *config_fields) + return ("type", *fields) def __init__(self): self._state: dict[str, Any] = {"type": self.DEFAULT_TYPE}