Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 89 additions & 74 deletions pyaml/accelerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -21,115 +19,132 @@
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)

self.post_init()

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)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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":
Expand Down
32 changes: 21 additions & 11 deletions pyaml/configuration/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import copy
import fnmatch
import inspect
import os
import re
from pathlib import Path
Expand Down Expand Up @@ -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}
Expand Down
Loading