From f2efebf63e8f84114a4bf189fa1275c7673126b2 Mon Sep 17 00:00:00 2001 From: Teresia Olsson Date: Thu, 13 Nov 2025 18:58:24 +0100 Subject: [PATCH 1/2] First commit of config layer with registry. --- examples/mls_example/mls_config.py | 57 +++++++++++++++++++ examples/mls_example/mls_registry.py | 18 ++++++ src/accml/core/config/__init__.py | 1 + src/accml/core/config/data_models/__init__.py | 3 + .../core/config/data_models/accelerator.py | 24 ++++++++ .../core/config/data_models/control_system.py | 29 ++++++++++ .../core/config/data_models/conversions.py | 22 +++++++ .../core/config/data_models/data_storage.py | 3 + src/accml/core/config/data_models/device.py | 52 +++++++++++++++++ .../core/config/data_models/simulator.py | 12 ++++ src/accml/core/config/model/__init__.py | 0 src/accml/core/config/model/magnet.py | 23 -------- .../core/config/model/power_converter.py | 32 ----------- src/accml/core/registry/accelerator.py | 56 ++++++++++++++++++ src/accml/core/registry/utils.py | 13 +++++ 15 files changed, 290 insertions(+), 55 deletions(-) create mode 100644 examples/mls_example/mls_config.py create mode 100644 examples/mls_example/mls_registry.py create mode 100644 src/accml/core/config/data_models/__init__.py create mode 100644 src/accml/core/config/data_models/accelerator.py create mode 100644 src/accml/core/config/data_models/control_system.py create mode 100644 src/accml/core/config/data_models/conversions.py create mode 100644 src/accml/core/config/data_models/data_storage.py create mode 100644 src/accml/core/config/data_models/device.py create mode 100644 src/accml/core/config/data_models/simulator.py delete mode 100644 src/accml/core/config/model/__init__.py delete mode 100644 src/accml/core/config/model/magnet.py delete mode 100644 src/accml/core/config/model/power_converter.py create mode 100644 src/accml/core/registry/accelerator.py create mode 100644 src/accml/core/registry/utils.py diff --git a/examples/mls_example/mls_config.py b/examples/mls_example/mls_config.py new file mode 100644 index 0000000..b6ad6cd --- /dev/null +++ b/examples/mls_example/mls_config.py @@ -0,0 +1,57 @@ +"""Example to create configuration for MLS using config classes.""" + +from accml.core.config import AcceleratorConfig, EPICSConfig, SimulatorConfig, MagnetConfig, EPICSType +from accml.core.registry.utils import WildcardDict + +#%% Facility and machine name +facility = 'MLS' +machine = 'storage_ring' + +#%% Control system modes +live = EPICSConfig(access_type=EPICSType.CA) +twin = EPICSConfig(access_type=EPICSType.CA,pv_prefix='twin') + +#%% Simulator modes +design = SimulatorConfig(type='pyat',model='design_lattice.json') +error = SimulatorConfig(type='pyat',model='error_lattice.json') +measured = SimulatorConfig(type='pyat',model='measured_lattice.json') + +#%% Devices + +# Quadrupoles +quads = [] +families = ['Q1','Q2','Q3'] +cells = ['1','2'] +sections = ['K1','L2','K3','L4'] + +for quad in families: + for cell in cells: + for section in sections: + quads.append(MagnetConfig(name=f'{quad}M{cell}{section}RP',type='quadrupole',power_supply=f'{quad}P{cell}{section}RP')) + +# Generate a dictionary of the quadrupole configurations +quads_by_name = {q.name: q for q in quads} + +#%% Families + +Q1 = [device.name for device in WildcardDict(quads_by_name)['Q1*']] +Q2 = [device.name for device in WildcardDict(quads_by_name)['Q2*']] +Q3 = [device.name for device in WildcardDict(quads_by_name)['Q3*']] +TuneCorrectors = Q1 + Q3 + +families = {'Q1': Q1, + 'Q2': Q2, + 'Q3': Q3, + 'TuneCorrectors': TuneCorrectors} + +#%% Accelerator + +config = AcceleratorConfig(facility=facility, machine=machine, + controls = {'live': live, 'twin': twin}, + simulators = {'design': design, + 'error': error, + 'measured': measured + }, + devices = quads_by_name, + families = families + ) \ No newline at end of file diff --git a/examples/mls_example/mls_registry.py b/examples/mls_example/mls_registry.py new file mode 100644 index 0000000..689ce00 --- /dev/null +++ b/examples/mls_example/mls_registry.py @@ -0,0 +1,18 @@ +"""Example how to use registry for MLS.""" + +from mls_config import config +from accml.core.registry.accelerator import Accelerator + + +#%% Create the accelerator object + +mls = Accelerator(config) + +#%% Extract information from the registry + +print(f'Facility: {mls.facility}\n') +print(f'Machine: {mls.machine}\n') +print(f'Controls: {mls.controls}\n') +print(f'Simulators: {mls.simulators}\n') +print(f'Devices: {mls.devices}\n') +print(f'Families: {mls.families}\n') \ No newline at end of file diff --git a/src/accml/core/config/__init__.py b/src/accml/core/config/__init__.py index e69de29..0a6bffe 100644 --- a/src/accml/core/config/__init__.py +++ b/src/accml/core/config/__init__.py @@ -0,0 +1 @@ +from .data_models import * \ No newline at end of file diff --git a/src/accml/core/config/data_models/__init__.py b/src/accml/core/config/data_models/__init__.py new file mode 100644 index 0000000..ebad505 --- /dev/null +++ b/src/accml/core/config/data_models/__init__.py @@ -0,0 +1,3 @@ +from .accelerator import * +from .control_system import * +from .device import * \ No newline at end of file diff --git a/src/accml/core/config/data_models/accelerator.py b/src/accml/core/config/data_models/accelerator.py new file mode 100644 index 0000000..a275a6e --- /dev/null +++ b/src/accml/core/config/data_models/accelerator.py @@ -0,0 +1,24 @@ +""" Configuration class for accelerator and families.""" + +from pydantic import BaseModel +from typing import Dict, List, Optional, Hashable +from .control_system import ControlSystemConfig +from .simulator import SimulatorConfig +from .device import DeviceConfig + +class AcceleratorConfig(BaseModel): + + facility: Optional[str] + machine: str + # TODO: data_storage + controls: Optional[Dict[Hashable, ControlSystemConfig]] = None + simulators: Optional[Dict[Hashable, SimulatorConfig]] = None + devices: Optional[Dict[Hashable, DeviceConfig]] = None + families: Optional[Dict[Hashable, List[Hashable]]] = None + # TODO: operational_modes + + +class FamilyConfig(BaseModel): + name: Hashable + devices: list[Hashable] + \ No newline at end of file diff --git a/src/accml/core/config/data_models/control_system.py b/src/accml/core/config/data_models/control_system.py new file mode 100644 index 0000000..6525fa0 --- /dev/null +++ b/src/accml/core/config/data_models/control_system.py @@ -0,0 +1,29 @@ +""" Configuration classes for control system.""" + +from abc import ABC +from pydantic import BaseModel +from typing import Optional +from enum import Enum + + +class ControlSystemConfig(BaseModel, ABC): + pass + + +class TANGOConfig(ControlSystemConfig): + host: str + + +class EPICSType(Enum): + + CA = 'CA' # Channel Access + PV = 'PV' # PV Access + + +class EPICSConfig(ControlSystemConfig): + access_type: EPICSType + pv_prefix: Optional[str] = "" + + +class DOOCSConfig(ControlSystemConfig): + pass diff --git a/src/accml/core/config/data_models/conversions.py b/src/accml/core/config/data_models/conversions.py new file mode 100644 index 0000000..07f979d --- /dev/null +++ b/src/accml/core/config/data_models/conversions.py @@ -0,0 +1,22 @@ +""" Configuration classes for conversions.""" + +# TODO: add different type of conversion models here + +from abc import ABC +from pydantic import BaseModel + +class ConversionConfig(BaseModel, ABC): + pass + + +class EnergyDependentConversionModel(ConversionConfig): + """Energy-dependent conversion model for magnetic objects. + + Todo: + - Add metadata describing the units + """ + + intercept: float # y-intercept of the calibration curve + slope: float # slope of the calibration curve + conversion_type: str # e.g., 'linear' + diff --git a/src/accml/core/config/data_models/data_storage.py b/src/accml/core/config/data_models/data_storage.py new file mode 100644 index 0000000..324e17d --- /dev/null +++ b/src/accml/core/config/data_models/data_storage.py @@ -0,0 +1,3 @@ +""" Configuration classes for data storage.""" + +# TODO: define data models for both storing as files and database. diff --git a/src/accml/core/config/data_models/device.py b/src/accml/core/config/data_models/device.py new file mode 100644 index 0000000..20b1a41 --- /dev/null +++ b/src/accml/core/config/data_models/device.py @@ -0,0 +1,52 @@ +""" Configuration classes for devices.""" + +from pydantic import BaseModel, Field +from abc import ABC +from typing import Hashable, Optional +from .conversions import ConversionConfig + +# TODO: figure out what is required for a device and what is same/different +# depending on the control system +# It needs to also be possible to configure devices from external modules +# in the same way. + +class DeviceConfig(BaseModel, ABC): + name: Hashable # e.g., 'QF1C01A' + # TODO: what is common for all devices? + + +class MagnetConfig(DeviceConfig): + magnet_type: str = Field(alias="type") + device_id: Optional[Hashable] = None# Engineering id, can be used for example to + # link to specific excitation curve or magnet model. + power_supply: Optional[Hashable] = None + conversion: Optional[ConversionConfig] = None + + +# class ResponseModel(BaseModel): +# """General response model for a device reacting to a control input change + +# Timeout: whithin this time the device has to answer +# Settle time: after this time the device is expected to be in a stable state +# """ + +# #: seconds +# timeout: float +# # seconds +# settle_time: float + + +# class PowerConverterInterface(BaseModel): +# #: e.g., 'CHANNEL:QF1C01A:SP' +# setpoint: str +# #: e.g., 'CHANNEL:QF1C01A:RB' +# readback: str + + +# class PowerConverter(BaseModel): +# id: Hashable +# interface: PowerConverterInterface +# response: ResponseModel + +# def get_current(self): +# return 0.0 \ No newline at end of file diff --git a/src/accml/core/config/data_models/simulator.py b/src/accml/core/config/data_models/simulator.py new file mode 100644 index 0000000..ce8541e --- /dev/null +++ b/src/accml/core/config/data_models/simulator.py @@ -0,0 +1,12 @@ +""" Configuration classes for simulators.""" + +from pydantic import BaseModel +from enum import Enum + +class SimulationEngine(Enum): + PYAT = 'pyat' + + +class SimulatorConfig(BaseModel): + type: SimulationEngine + model: str # Path to the lattice model. \ No newline at end of file diff --git a/src/accml/core/config/model/__init__.py b/src/accml/core/config/model/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/accml/core/config/model/magnet.py b/src/accml/core/config/model/magnet.py deleted file mode 100644 index 9ea293b..0000000 --- a/src/accml/core/config/model/magnet.py +++ /dev/null @@ -1,23 +0,0 @@ -from pydantic import BaseModel -from typing import Hashable, Sequence - - -class EnergyDependentConversionModel(BaseModel): - """Energy-dependent conversion model for magnetic objects. - - Todo: - - Add metadata describing the units - """ - - intercept: float # y-intercept of the calibration curve - slope: float # slope of the calibration curve - conversion_type: str # e.g., 'linear' - - -class MagneticObject(BaseModel): - elem_id: Hashable # e.g., 'QF1C01A' - dev_id: Hashable # e.g., 'QF1C01A' or 'QF1C01' - type: str # e.g., 'quadrupole' - family_member: Sequence[str] - power_converter_id: Hashable # reference to PowerConverter.id - conversion: EnergyDependentConversionModel diff --git a/src/accml/core/config/model/power_converter.py b/src/accml/core/config/model/power_converter.py deleted file mode 100644 index 58c9b64..0000000 --- a/src/accml/core/config/model/power_converter.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Hashable - -from pydantic import BaseModel - - -class ResponseModel(BaseModel): - """General response model for a device reacting to a control input change - - Timeout: whithin this time the device has to answer - Settle time: after this time the device is expected to be in a stable state - """ - - #: seconds - timeout: float - # seconds - settle_time: float - - -class PowerConverterInterface(BaseModel): - #: e.g., 'CHANNEL:QF1C01A:SP' - setpoint: str - #: e.g., 'CHANNEL:QF1C01A:RB' - readback: str - - -class PowerConverter(BaseModel): - id: Hashable - interface: PowerConverterInterface - response: ResponseModel - - def get_current(self): - return 0.0 diff --git a/src/accml/core/registry/accelerator.py b/src/accml/core/registry/accelerator.py new file mode 100644 index 0000000..3ebdad7 --- /dev/null +++ b/src/accml/core/registry/accelerator.py @@ -0,0 +1,56 @@ +""" Module for the AO and registry functionality.""" + +from accml.core.config import AcceleratorConfig +from accml.core.registry.utils import WildcardDict + +class Accelerator(): + + def __init__(self, config: AcceleratorConfig): + + self._facility = config.facility + self._machine = config.machine + self._controls = config.controls + self._simulators = config.simulators + self._devices = WildcardDict(config.devices) # Dict which can use wildcards + self._families = config.families + + + @property + def facility(self): + return self._facility + + @property + def machine(self): + return self._machine + + @property + def controls(self): + if self._controls: + return self._controls + else: + print('No controls have been configured.') + + @property + def simulators(self): + if self._simulators: + return self._simulators + else: + print('No simulators have been configured.') + + @property + def devices(self): + if self._devices: + return self._devices + else: + print('No devices have been configured.') + + @property + def families(self): + if self._families: + return self._families + else: + print('No families have been configured.') + + def __str__(self): + """Pretty printing of the accelerator configuration.""" + pass diff --git a/src/accml/core/registry/utils.py b/src/accml/core/registry/utils.py new file mode 100644 index 0000000..b2c671e --- /dev/null +++ b/src/accml/core/registry/utils.py @@ -0,0 +1,13 @@ +""" Module for registry utility functionality. """ + +from collections import UserDict +import fnmatch +from typing import List + +class WildcardDict(UserDict): + """ + Dictionary where keys can be found using wildcards. + """ + + def __getitem__(self, pattern: str) -> List: + return [v for k, v in self.data.items() if fnmatch.fnmatch(str(k), pattern)] \ No newline at end of file From 5216e32032254b35c5ce56acdaec781cec079aa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Nov 2025 09:44:36 +0000 Subject: [PATCH 2/2] Initial plan