Skip to content

Bug: lattice_names is ignored when a linker is configured on the simulator #429

Description

@GamelinAl

I have run into an issue when trying to make a configuration for SOLEIL using the attribute linker, which is not present when not using the attribute linked.
Here is the description of the issue and an exemple to reproduce this issue.
There is a proposed fix which seem to work so I'll make a MR about it.

I also noticed that currently it's not possible to "combine" CFM and serialized magnets at the same time, which might also need another separate issue.

Problem

When a pyaml.lattice.simulator is configured with a linker (e.g. pyaml.lattice.attribute_linker matching on a UUID attribute), a pyAML element can only be bound to the PyAT elements whose attribute equals the element's own name. The lattice_names selector documented on Element ("A pyAML element can be associated to several lattice elements", list(name,[name]), name@idx, name#a..b) is silently ignored:

# pyaml/lattice/simulator.py, Simulator.get_at_elems
if self._linker:
    identifier = self._linker.get_element_identifier(element)   # <- built from element.get_name() only
    element_list = self._linker.get_at_elements(identifier)
    ...
    return element_list
else:
    # By list  -> lattice_names is only parsed here
    nameList = self.get_names(element)
# pyaml/lattice/attribute_linker.py, PyAtAttributeElementsLinker.get_element_identifier
return PyAtAttributeIdentifier(self.linker_config_model.attribute_name, element.get_name())

Consequence: with a linker, one PyAT element can be driven by at most one pyAML element, because element names are unique in the registry. This blocks a common real-machine layout: a sextupole whose yoke carries H/V corrector coils (SOLEIL case) modelled as one thick PyAT element, where the main function is a serialized_magnet member (one power supply per family) and the coils are separate hcorrector / vcorrector elements with their own power supplies. The cfm_magnet alternative cannot express the serialized (shared power supply) main function.

Minimal example

mini.m (generated with pyAT):

import at
ring = at.Lattice([at.Drift('D1', 1.0),
                   at.Sextupole('SX', 0.16, 1.0, UUID='SX_001'),   # sextupole with corrector coils inside
                   at.Drift('D2', 1.0)],
                  name='mini', energy=2.75e9, periodicity=1)
at.save_m(ring, 'mini.m')

sr.yaml:

type: pyaml.accelerator
facility: DEMO
machine: sr
energy: 2.75e9
simulators:
  - type: pyaml.lattice.simulator
    name: design
    lattice: mini.m
    linker:
      type: pyaml.lattice.attribute_linker
      attribute_name: UUID
devices:
  - type: pyaml.magnet.sextupole
    name: SX_001
    model: {type: pyaml.magnet.identity_model, physics: sr/ps/sx/current, unit: 1/m**2}
  - type: pyaml.magnet.hcorrector
    name: SX_001_H
    lattice_names: list(SX_001)      # the H coil sits on the same PyAT element
    model: {type: pyaml.magnet.identity_model, physics: sr/ps/sx-ch/current, unit: rad}
from pyaml.accelerator import Accelerator
Accelerator.load('sr.yaml', ignore_external=True)
pyaml.common.exception.PyAMLConfigException: No PyAT elements found for identifier(s): UUID=SX_001_H when creating 'pyaml.accelerator.Accelerator'

lattice_names: list(SX_001) was never looked at: the linker searched for UUID == "SX_001_H".

Without the linker: block (default FamName linking, name: SX + lattice_names: list(SX)) the same configuration loads and both elements drive the same PyAT element:

SX_001_H strength 0.0 | SX strength 0.16
after h.strength.set(1e-3): PolynomB = [-0.00625  0.  1.]

Proposal

Make Simulator.get_at_elems honour the list(...) form of lattice_names when a linker is set, by asking the linker for one identifier per listed name. LatticeElementsLinker.get_at_elements already accepts a list of identifiers, so the change is small:

# pyaml/lattice/lattice_elements_linker.py  (abstract)
def get_identifier_for_name(self, name: str) -> LinkerIdentifier:
    """Identifier matching the PyAT element(s) designated by ``name`` in ``lattice_names``."""
    raise NotImplementedError

# pyaml/lattice/attribute_linker.py
def get_identifier_for_name(self, name: str) -> LinkerIdentifier:
    return PyAtAttributeIdentifier(self.linker_config_model.attribute_name, name)

# pyaml/lattice/simulator.py, Simulator.get_at_elems
if self._linker:
    names = self.get_names(element)                     # lattice_names: list(a,b) -> [a, b]; else None
    if names is None:
        identifiers = self._linker.get_element_identifier(element)
    else:
        identifiers = [self._linker.get_identifier_for_name(n) for n in names]
    element_list = self._linker.get_at_elements(identifiers)
    if not element_list:
        raise PyAMLException(f"{identifiers} not found in lattice:{self._lattice}")
    return element_list

With this change the example above loads with the linker, and:

with linker + patch: SX_001_H strength 0.0 | SX_001 strength 0.16
after h.strength.set(1e-3): PolynomB = [-0.00625  0.  1.]

What needs to follow

The other synatx that pyAML lattice_names selector allow do not work either.
The @idx / #a..b forms could be supported the same way (get_at_elements(identifier) then index into the result), but list(...) is the form needed to share a PyAT element between elements.
Element.lattice_names docstring could mention that, with a linker, the listed names are matched against the linker attribute instead of FamName.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingenhancementNew feature or requesthelp wantedExtra attention is needed

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions