Live/Design: control modes#

With pyAML it is possible to use the same commands either:

  1. live: on the real accelerator

  2. design: in simulations

  3. errors: in simulations with errors (and for several seeds)

  4. live (with a different prefix/host): in a virtual control system (same as in point 1. for those laboratories that have a simulated control system)

Those are called “control modes” and may be used

  • at a global script level (for testing in simulations and then using in real accelerator measurements with a simple switch/comment)

  • anywhere in the script

Using pyAML on a real accelerator

import os

import numpy as np

from pyaml.accelerator import Accelerator

instantiate a pyAML accelerator

SR: Accelerator = Accelerator.load("../../tests/config/EBSTune.yaml")
---------------------------------------------------------------------------
PyAMLConfigException                      Traceback (most recent call last)
Cell In[2], line 1
----> 1 SR: Accelerator = Accelerator.load("../../tests/config/EBSTune.yaml")

File ~/checkouts/readthedocs.org/user_builds/pyaml/envs/latest/lib/python3.13/site-packages/pyaml/accelerator.py:304, in Accelerator.load(filename, use_fast_loader, ignore_external)
    299 except UnsupportedConfigurationRootError as ex:
    300     raise PyAMLConfigException(
    301         "Accelerator.load() expects a 'pyaml.accelerator' root configuration. "
    302         "Use the factory APIs to build sub-elements directly."
    303     ) from ex
--> 304 return manager.build(ignore_external=ignore_external)

File ~/checkouts/readthedocs.org/user_builds/pyaml/envs/latest/lib/python3.13/site-packages/pyaml/configuration/manager.py:496, in ConfigurationManager.build(self, ignore_external)
    494     set_root_folder(self._build_root)
    495 snapshot = ConfigurationManager.strip_runtime_internal_metadata(self._snapshot(include_internal_metadata=True))
--> 496 return Accelerator.from_dict(snapshot, ignore_external=ignore_external)

File ~/checkouts/readthedocs.org/user_builds/pyaml/envs/latest/lib/python3.13/site-packages/pyaml/accelerator.py:275, in Accelerator.from_dict(config_dict, ignore_external)
    273 # Ensure factory is clean before building a new accelerator
    274 Factory.clear()
--> 275 return Factory.build(config_dict, ignore_external)

File ~/checkouts/readthedocs.org/user_builds/pyaml/envs/latest/lib/python3.13/site-packages/pyaml/configuration/factory.py:548, in PyAMLFactory.build(self, data, ignore_external)
    545     return self._build_list(data, ignore_external)
    547 elif isinstance(data, dict):
--> 548     data = self._build_dict(data, ignore_external)
    549     return self.build_object(data, ignore_external)

File ~/checkouts/readthedocs.org/user_builds/pyaml/envs/latest/lib/python3.13/site-packages/pyaml/configuration/factory.py:395, in PyAMLFactory._build_dict(self, data, ignore_external)
    392         # Do not recurse dict defined in ConfigModel
    393         # pydantic use TypedDict not usable with isinstance
    394         if not self._is_plain_dict_type(field_type):
--> 395             result[key] = self.build(value, ignore_external)
    397 return result

File ~/checkouts/readthedocs.org/user_builds/pyaml/envs/latest/lib/python3.13/site-packages/pyaml/configuration/factory.py:545, in PyAMLFactory.build(self, data, ignore_external)
    542     raise PyAMLConfigException(f"Unexpected element found. 'dict' or 'list' expected but got '{type(data).__name__}'")
    544 if isinstance(data, list):
--> 545     return self._build_list(data, ignore_external)
    547 elif isinstance(data, dict):
    548     data = self._build_dict(data, ignore_external)

File ~/checkouts/readthedocs.org/user_builds/pyaml/envs/latest/lib/python3.13/site-packages/pyaml/configuration/factory.py:360, in PyAMLFactory._build_list(self, items, ignore_external)
    355 def _build_list(self, items, ignore_external: bool):
    356     """
    357     Recursively build all nested objects contained in a list.
    358     """
--> 360     return [self.build(item, ignore_external) if isinstance(item, (dict, list)) else item for item in items]

File ~/checkouts/readthedocs.org/user_builds/pyaml/envs/latest/lib/python3.13/site-packages/pyaml/configuration/factory.py:548, in PyAMLFactory.build(self, data, ignore_external)
    545     return self._build_list(data, ignore_external)
    547 elif isinstance(data, dict):
--> 548     data = self._build_dict(data, ignore_external)
    549     return self.build_object(data, ignore_external)

File ~/checkouts/readthedocs.org/user_builds/pyaml/envs/latest/lib/python3.13/site-packages/pyaml/configuration/factory.py:380, in PyAMLFactory._build_dict(self, data, ignore_external)
    371 def _build_dict(self, data: dict, ignore_external: bool):
    372     """
    373     Recursively build nested objects referenced by a configuration
    374     dictionary.
   (...)    377     recursive build process and are left unchanged.
    378     """
--> 380     build_info = resolve_build_info(data, ignore_external)
    381     config_cls = build_info.config_cls
    383     result = dict(data)

File ~/checkouts/readthedocs.org/user_builds/pyaml/envs/latest/lib/python3.13/site-packages/pyaml/configuration/factory.py:238, in resolve_build_info(data, ignore_external)
    236     if ignore_external:
    237         return None
--> 238     raise PyAMLConfigException(f"Module referenced in type cannot be found: '{module_str}' {location_str}") from None
    240 # Get the object class name
    241 if class_str is None:

PyAMLConfigException: Module referenced in type cannot be found: 'tango.pyaml.controlsystem' /home/docs/checkouts/readthedocs.org/user_builds/pyaml/checkouts/latest/tests/config/EBSTune.yaml at line 12, column 5.

Switch between live and design

Access some magnet families defined in the configuration is simple. One can use sr.live for live control system or sr.design for the simulator part. They provide identical interface.

sr = SR.design  # simulations
# sr = SR.live # act on real accelerator
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[3], line 1
----> 1 sr = SR.design  # simulations
      2 # sr = SR.live # act on real accelerator

NameError: name 'SR' is not defined
quadForTune = sr.get_magnets("QForTune")

tune_device = sr.get_betatron_tune_monitor("BETATRON_TUNE")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[4], line 1
----> 1 quadForTune = sr.get_magnets("QForTune")
      2 
      3 tune_device = sr.get_betatron_tune_monitor("BETATRON_TUNE")

NameError: name 'sr' is not defined

For example a tune response matrix is evaluated below. This script is used identically for computation of theoretical values or to measure the response directly on the accelerator.

# Build tune response matrix
initial_tune = tune_device.tune.get()
print(f"Tune via pyAML interface for design mode {initial_tune}")
tunemat = np.zeros((len(quadForTune), 2))

for idx, m in enumerate(quadForTune):
    str = m.strength.get()
    m.strength.set(str + 1e-4)
    dq = tune_device.tune.get() - initial_tune
    tunemat[idx] = dq * 1e4
    m.strength.set(str)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[5], line 2
      1 # Build tune response matrix
----> 2 initial_tune = tune_device.tune.get()
      3 print(f"Tune via pyAML interface for design mode {initial_tune}")
      4 tunemat = np.zeros((len(quadForTune), 2))
      5 

NameError: name 'tune_device' is not defined

At any point in the code it is always possible to still access any of the control modes

SR.design.get_lattice().disable_6d()
tune_design = SR.design.get_lattice().get_tune()

print(f"Tune directly from the lattice {tune_design}")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[6], line 1
----> 1 SR.design.get_lattice().disable_6d()
      2 tune_design = SR.design.get_lattice().get_tune()
      3 
      4 print(f"Tune directly from the lattice {tune_design}")

NameError: name 'SR' is not defined