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
29 changes: 29 additions & 0 deletions .github/workflows/pySC_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Tests

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
tests:
runs-on: ubuntu-latest

strategy:
matrix:
python-version: ["3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install package with test dependencies
run: pip install ".[test]"

- name: Run tests
run: pytest --cov
40 changes: 40 additions & 0 deletions .github/workflows/pyaml_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: pyAML tests

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
tests:
runs-on: ubuntu-latest

strategy:
matrix:
python-version: ["3.12", "3.14"]

steps:
- uses: actions/checkout@v4

- name: Clone second repository
uses: actions/checkout@v4
with:
repository: python-accelerator-middle-layer/pyaml
path: pyaml
ref: main

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install package with test dependencies
run: |
pip install "./pyaml[test]"
pip install ./pyaml/tests/dummy_cs/tango-pyaml
pip install ".[test]"

- name: Run tests
working-directory: pyaml
run: pytest -k "tuning"
59 changes: 52 additions & 7 deletions pySC/configuration/magnets_conf.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Any
from ..core.simulated_commissioning import SimulatedCommissioning
from ..core.lattice import ATLattice
from ..core.magnet import MAGNET_NAME_TYPE
from ..core.control import LinearConv
from ..core.magnet import ControlMagnetLink, MAGNET_NAME_TYPE
from .general import get_error, get_indices_and_names
from .supports_conf import generate_element_misalignments

Expand All @@ -18,6 +19,14 @@ def generate_default_magnet_control(SC: SimulatedCommissioning, index: int, magn
components_to_invert = dict.get(magnet_category_conf, 'invert', []).copy() # defaults to empty list if not declared
# we need to copy because we remove elements later to check for undeclared components to invert

is_shifted = magnet_category_conf.get('shifted', False)
magnet_length = SC.lattice.get_length(index)
if is_shifted and not SC.lattice.is_dipole(index):
raise ValueError(
f"magnets/{magnet_category_name}: shifted=true expects a dipole "
f"at index {index} ({magnet_name})."
)

if 'components' in magnet_category_conf:
components = []
cal_errors = []
Expand All @@ -26,11 +35,13 @@ def generate_default_magnet_control(SC: SimulatedCommissioning, index: int, magn
components.append(component)
cal_errors.append(cal_error)

magnet_length = SC.lattice.get_length(index)
magnet_settings.add_individually_powered_magnet(
sim_index=index, controlled_components=components,
magnet_name=magnet_name, magnet_length=magnet_length,
to_design=to_design)
sim_index=index,
controlled_components=components,
magnet_name=magnet_name,
magnet_length=magnet_length,
to_design=to_design
)

for component, cal_error in zip(components, cal_errors):
control_name = f'{magnet_name}/{component}'
Expand Down Expand Up @@ -59,7 +70,7 @@ def generate_default_magnet_control(SC: SimulatedCommissioning, index: int, magn
offset = 0
setpoint = SC.lattice.get_magnet_component(index, component_type=component_type, order=order)
if component[-1] == 'L':
length = SC.lattice.get_length(index)
length = magnet_length
setpoint = setpoint * length

if component in components_to_invert:
Expand All @@ -70,6 +81,40 @@ def generate_default_magnet_control(SC: SimulatedCommissioning, index: int, magn
magnet_settings.links[link_name].error.factor = factor
magnet_settings.links[link_name].error.offset = offset

if is_shifted:
quad_components = [component for component in components if component in ('B2', 'B2L')]
if len(quad_components) != 1:
raise ValueError(f"magnets/{magnet_category_name}: shifted=true requires B2 or B2L in control")

component = quad_components[0]
control_name = f'{magnet_name}/{component}'
source_link = magnet_settings.links[f'{control_name}->{control_name}']
k1 = SC.lattice.get_magnet_component(index, component_type='B', order=1)
if k1 == 0:
raise ValueError(f"magnets/{magnet_category_name}: shifted=true requires a non-zero quadrupole component at index {index} ({magnet_name}).")

k0 = SC.lattice.get_magnet_component(index, component_type='B', order=0)
reference_k0 = 0.0
if type(SC.lattice) is ATLattice:
reference_k0 = SC.lattice.get_bending_angle(index) / magnet_length
k0 += reference_k0
design_shift = k0 / k1

scale = magnet_length if source_link.is_integrated else 1.0
factor = design_shift * source_link.error.factor / scale
offset = design_shift * source_link.error.offset / scale - reference_k0
target_name = f'{magnet_name}/B1'
magnet_settings.add_link(ControlMagnetLink(
link_name=f'{control_name}->{target_name}',
magnet_name=magnet_name,
control_name=control_name,
component='B',
order=1,
error=LinearConv(factor=factor, offset=offset),
is_integrated=False,
))
magnet_settings.magnets[magnet_name].offset_B[0] = 0.0

assert len(components_to_invert) == 0, f"Found undeclared components in components to invert: magnets/{magnet_category_name}/invert: {components_to_invert}."


Expand Down Expand Up @@ -113,4 +158,4 @@ def configure_magnets(SC: SimulatedCommissioning):
SC.magnet_settings.connect_links()
SC.magnet_settings.sendall()
SC.design_magnet_settings.connect_links()
SC.design_magnet_settings.sendall()
SC.design_magnet_settings.sendall()
48 changes: 48 additions & 0 deletions tests/configuration/test_magnets_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,54 @@ def test_configure_magnets_dipole_convention(hmba_lattice_file):
assert link.error.offset == pytest.approx(expected_offset, rel=1e-6)


@pytest.mark.slow
@pytest.mark.parametrize("component", ["B2", "B2L"])
def test_configure_shifted_quadrupole_feed_down(hmba_lattice_file, component):
lattice = ATLattice(lattice_file=hmba_lattice_file, naming="FamName")
dip_name = "DQ1B"
dip_index = 50
initial_angle = lattice.get_bending_angle(dip_index)
initial_b0 = lattice.get_magnet_component(dip_index, "B", 0)
initial_k1 = lattice.get_magnet_component(dip_index, "B", 1)
length = lattice.get_length(dip_index)
design_shift = (initial_angle / length + initial_b0) / initial_k1

config = {
"error_table": {"quad_cal": "0"},
"magnets": {
"shifted_quadrupole": {
"regex": f"^{dip_name}$",
"components": [{component: "quad_cal"}],
"shifted": True,
},
},
}
SC = SimulatedCommissioning(lattice=lattice, configuration=config, seed=42)
configure_magnets(SC)

control_name = f"{dip_name}/{component}"
feed_down_link = SC.magnet_settings.links[f"{control_name}->{dip_name}/B1"]
assert feed_down_link.is_integrated is False
assert SC.lattice.get_bending_angle(dip_index) == pytest.approx(initial_angle)
assert SC.lattice.get_magnet_component(
dip_index, "B", 0, use_design=False
) == pytest.approx(initial_b0)

delta_k1 = 0.2
setpoint = initial_k1 + delta_k1
if component == "B2L":
setpoint *= length
SC.magnet_settings.set(control_name, setpoint)

assert SC.lattice.get_bending_angle(dip_index) == pytest.approx(initial_angle)
assert SC.lattice.get_magnet_component(
dip_index, "B", 1, use_design=False
) == pytest.approx(initial_k1 + delta_k1)
assert SC.lattice.get_magnet_component(
dip_index, "B", 0, use_design=False
) == pytest.approx(initial_b0 + design_shift * delta_k1)


@pytest.mark.slow
def test_configure_magnets_limits(hmba_lattice_file):
"""Controls have limits when configured."""
Expand Down
3 changes: 1 addition & 2 deletions tests/core/test_magnet.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Tests for pySC.core.magnet: Magnet, ControlMagnetLink."""
import pytest
from unittest.mock import MagicMock, PropertyMock

from unittest.mock import MagicMock
from pySC.core.magnet import Magnet, ControlMagnetLink
from pySC.core.control import Control, LinearConv

Expand Down
Loading