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.
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.simulatoris configured with a linker (e.g.pyaml.lattice.attribute_linkermatching on aUUIDattribute), a pyAML element can only be bound to the PyAT elements whose attribute equals the element's own name. Thelattice_namesselector documented onElement("A pyAML element can be associated to several lattice elements",list(name,[name]),name@idx,name#a..b) is silently ignored: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_magnetmember (one power supply per family) and the coils are separatehcorrector/vcorrectorelements with their own power supplies. Thecfm_magnetalternative cannot express the serialized (shared power supply) main function.Minimal example
mini.m(generated with pyAT):sr.yaml:lattice_names: list(SX_001)was never looked at: the linker searched forUUID == "SX_001_H".Without the
linker:block (defaultFamNamelinking,name: SX+lattice_names: list(SX)) the same configuration loads and both elements drive the same PyAT element:Proposal
Make
Simulator.get_at_elemshonour thelist(...)form oflattice_nameswhen a linker is set, by asking the linker for one identifier per listed name.LatticeElementsLinker.get_at_elementsalready accepts a list of identifiers, so the change is small:With this change the example above loads with the linker, and:
What needs to follow
The other synatx that pyAML
lattice_namesselector allow do not work either.The
@idx/#a..bforms could be supported the same way (get_at_elements(identifier)then index into the result), butlist(...)is the form needed to share a PyAT element between elements.Element.lattice_namesdocstring could mention that, with a linker, the listed names are matched against the linker attribute instead ofFamName.