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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dependencies = [
"scanspec>=0.7.3",
"pyzmq==26.3.0", # Until we can move to RHEL 8 https://github.com/DiamondLightSource/mx-bluesky/issues/1139
"deepdiff",
"daq-config-server>=v1.0.0", # For getting Configuration settings.
"daq-config-server>=v1.1.2",
]

dynamic = ["version"]
Expand Down
13 changes: 9 additions & 4 deletions src/dodal/devices/undulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import numpy as np
from bluesky.protocols import Locatable, Location, Movable
from daq_config_server.client import ConfigServer
from daq_config_server.models import UndulatorEnergyGapLookupTable
from numpy import ndarray
from ophyd_async.core import (
AsyncStatus,
Expand All @@ -19,7 +21,6 @@
from dodal.log import LOGGER

from .baton import Baton
from .util.lookup_tables import energy_distance_table


class AccessError(Exception):
Expand Down Expand Up @@ -185,6 +186,7 @@ def __init__(
baton (optional): Baton object if provided.
name (str, optional): Name for device. Defaults to "".
"""
self.config_server = ConfigServer(url="https://daq-config.diamond.ac.uk")

Comment on lines +189 to 190
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if this hardcoded config server is a good solution long term. I understand that it works fine now, but what if for some reason you need to divert your config server to a different endpoint - you would have to change the path here.
It feels like we need some king of an interface or a lookuptable provider which should probably be configured separately.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snap! I think my comment at #1773 (comment) is basically the same thing. I think we should define it in the ixx.py files like we do for e.g. a path provider

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, agreed - in an even broader perspective I don't think we should need daq-config-server as a dependency - but that's for later development DiamondLightSource/daq-config-server#157

self.id_gap_lookup_table_path = id_gap_lookup_table_path
super().__init__(
Expand Down Expand Up @@ -216,14 +218,17 @@ async def _get_gap_to_match_energy(self, energy_kev: float) -> float:
get a 2d np.array from lookup table that
converts energies to undulator gap distance
"""
energy_to_distance_table: np.ndarray = await energy_distance_table(
self.id_gap_lookup_table_path

energy_to_distance_table = self.config_server.get_file_contents(
self.id_gap_lookup_table_path,
UndulatorEnergyGapLookupTable,
reset_cached_result=True,
)

# Use the lookup table to get the undulator gap associated with this dcm energy
return _get_gap_for_energy(
energy_kev * 1000,
energy_to_distance_table,
np.array(energy_to_distance_table.rows),
)


Expand Down
30 changes: 30 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import importlib
import json
import logging
import os
import sys
Expand All @@ -8,6 +9,7 @@
from unittest.mock import MagicMock, patch

import pytest
from daq_config_server.models import ConfigModel
from ophyd_async.core import PathProvider

from dodal.common.beamlines import beamline_parameters, beamline_utils
Expand Down Expand Up @@ -159,3 +161,31 @@ def eiger_params(tmp_path: Path) -> DetectorParams:
det_dist_to_beam_converter_path=TEST_LUT_TXT,
detector_size_constants=EIGER2_X_16M_SIZE.det_type_string, # type: ignore
)


def _fake_config_server_get_file_contents(
filepath: str | Path,
desired_return_type: type[str] | type[dict] | ConfigModel = str,
reset_cached_result: bool = True,
):
filepath = Path(filepath)
# Minimal logic required for unit tests
with filepath.open("r") as f:
contents = f.read()
if desired_return_type is str:
return contents
elif desired_return_type is dict:
return json.loads(contents)
elif issubclass(desired_return_type, ConfigModel): # type: ignore
return desired_return_type.model_validate(json.loads(contents))


@pytest.fixture(autouse=True)
def mock_config_server():
# Don't actually talk to central service during unit tests, and reset caches between test

with patch(
"daq_config_server.client.ConfigServer.get_file_contents",
side_effect=_fake_config_server_get_file_contents,
):
yield
16 changes: 12 additions & 4 deletions tests/devices/i03/test_undulator_dcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import numpy as np
import pytest
from daq_config_server.models import UndulatorEnergyGapLookupTable
from ophyd_async.core import AsyncStatus, get_mock_put, init_devices, set_mock_value

from dodal.common.enums import EnabledDisabledUpper
Expand Down Expand Up @@ -79,16 +80,23 @@ async def test_fixed_offset_decoded(fake_undulator_dcm: UndulatorDCM):
assert fake_undulator_dcm.dcm_fixed_offset_mm == 25.6


@patch("dodal.devices.util.lookup_tables.loadtxt")
@patch("dodal.devices.undulator.LOGGER")
@patch("dodal.devices.undulator.ConfigServer.get_file_contents")
async def test_if_gap_is_wrong_then_logger_info_is_called_and_gap_is_set_correctly(
mock_logger: MagicMock, mock_load: MagicMock, fake_undulator_dcm: UndulatorDCM
mock_get_file_contents: MagicMock,
mock_logger: MagicMock,
fake_undulator_dcm: UndulatorDCM,
):
mock_get_file_contents.return_value = UndulatorEnergyGapLookupTable(
rows=[
[5700, 5.4606],
[7000, 6.045],
[9700, 6.404],
],
)
set_mock_value(fake_undulator_dcm.undulator_ref().current_gap, 5.3)
set_mock_value(fake_undulator_dcm.dcm_ref().energy_in_keV.user_readback, 5.7)

mock_load.return_value = np.array([[5700, 5.4606], [7000, 6.045], [9700, 6.404]])

await fake_undulator_dcm.set(6.9)

assert (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
# Bragg pitch
# Degree values for pitch are interpreted as mrad
# The values cannot change direction.
# last update 2023/06/26 NP
Units Deg mrad
Units Deg Deg
19.24347 -0.79775
16.40949 -0.78679
14.31123 -0.77838
12.69287 -0.77276
11.40555 -0.77276
10.35662 -0.77031
9.48522 -0.76693
8.95826 -0.76387
8.74953 -0.76387
8.12020 -0.76387
7.57556 -0.76354
7.09950 -0.76166
6.67997 -0.76044
6.30732 -0.75953
5.97411 -0.75845
5.67434 -0.75796
5.40329 -0.75789
5.15700 -0.75551
4.93218 -0.75513
{
"rows": [
[19.24347, -0.79775],
[16.40949, -0.78679],
[14.31123, -0.77838],
[12.69287, -0.77276],
[11.40555, -0.77276],
[10.35662, -0.77031],
[9.48522, -0.76693],
[8.95826, -0.76387],
[8.74953, -0.76387],
[8.1202, -0.76387],
[7.57556, -0.76354],
[7.0995, -0.76166],
[6.67997, -0.76044],
[6.30732, -0.75953],
[5.97411, -0.75845],
[5.67434, -0.75796],
[5.40329, -0.75789],
[5.157, -0.75551],
[4.93218, -0.75513]
]
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Bragg angle against roll( absolute number)
#reloadLookupTables()
# last update 2023/01/19 NP
Units Deg mrad
26.4095 -0.2799
6.3075 -0.2799
{
"rows": [
[26.4095, -0.2799],
[6.3075, -0.2799]
]
}
Original file line number Diff line number Diff line change
@@ -1,60 +1,54 @@
#######################
# #
# 5.5mm CPMU 20/11/22 #
# #
#######################
# Used to convert from energy to gap. Constructed from tables for 3rd, 5th and 7th harmonic.
# It is important that at the point of change from one harmonic to another that there is
# point for the same energy from both harmomics to prevent invalid interpolation.
# run reloadLookupTables() when done
Units eV mm
5700 5.4606
5760 5.5
6000 5.681
6500 6.045
7000 6.404
7500 6.765
8000 7.124
8500 7.491
9000 7.872
9500 8.258
9700 8.424
9700 5.542
10000 5.675
10500 5.895
11000 6.113
11500 6.328
12000 6.545
12500 6.758
12700 6.843
13000 6.98
13443 7.168
13443 5.5
13500 5.517
14000 5.674
14500 5.831
15000 5.987
15500 6.139
16000 6.294
16500 6.447
17000 6.603
17320 6.697
17320 5.5
17500 5.552
18000 5.674
18500 5.794
19000 5.912
19500 6.037
20000 6.157
20500 6.277
20939 6.378
20939 5.5
21000 5.517
21500 5.577
22000 5.674
22500 5.773
23000 5.871
23500 5.97
24000 6.072
24500 6.167
25000 6.264
{
"rows": [
[5700, 5.4606],
[5760, 5.5],
[6000, 5.681],
[6500, 6.045],
[7000, 6.404],
[7500, 6.765],
[8000, 7.124],
[8500, 7.491],
[9000, 7.872],
[9500, 8.258],
[9700, 8.424],
[9700, 5.542],
[10000, 5.675],
[10500, 5.895],
[11000, 6.113],
[11500, 6.328],
[12000, 6.545],
[12500, 6.758],
[12700, 6.843],
[13000, 6.98],
[13443, 7.168],
[13443, 5.5],
[13500, 5.517],
[14000, 5.674],
[14500, 5.831],
[15000, 5.987],
[15500, 6.139],
[16000, 6.294],
[16500, 6.447],
[17000, 6.603],
[17320, 6.697],
[17320, 5.5],
[17500, 5.552],
[18000, 5.674],
[18500, 5.794],
[19000, 5.912],
[19500, 6.037],
[20000, 6.157],
[20500, 6.277],
[20939, 6.378],
[20939, 5.5],
[21000, 5.517],
[21500, 5.577],
[22000, 5.674],
[22500, 5.773],
[23000, 5.871],
[23500, 5.97],
[24000, 6.072],
[24500, 6.167],
[25000, 6.264]
]
}
21 changes: 12 additions & 9 deletions tests/devices/test_undulator.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
from unittest.mock import MagicMock, patch

import numpy as np
import pytest
from bluesky import RunEngine
from bluesky.plan_stubs import mv
from daq_config_server.models import UndulatorEnergyGapLookupTable
from ophyd_async.core import get_mock_put, init_devices, set_mock_value
from ophyd_async.testing import (
assert_configuration,
Expand Down Expand Up @@ -151,13 +152,14 @@ async def test_when_gap_access_is_disabled_set_then_error_is_raised(
await undulator.set(5)


@patch(
"dodal.devices.undulator.energy_distance_table",
AsyncMock(return_value=np.array([[0, 10], [10, 20]])),
)
@patch("dodal.devices.undulator.ConfigServer.get_file_contents")
async def test_gap_access_check_disabled_and_move_inhibited_when_commissioning_mode_enabled(
mock_get_file_contents: MagicMock,
undulator_in_commissioning_mode: UndulatorInKeV,
):
mock_get_file_contents.return_value = UndulatorEnergyGapLookupTable(
rows=[[0, 10], [10, 20]]
)
set_mock_value(
undulator_in_commissioning_mode.gap_access, EnabledDisabledUpper.DISABLED
)
Expand All @@ -168,13 +170,14 @@ async def test_gap_access_check_disabled_and_move_inhibited_when_commissioning_m
).assert_not_called()


@patch(
"dodal.devices.undulator.energy_distance_table",
AsyncMock(return_value=np.array([[0, 10], [10000, 20]])),
)
@patch("dodal.devices.undulator.ConfigServer.get_file_contents")
async def test_gap_access_check_move_not_inhibited_when_commissioning_mode_disabled(
mock_get_file_contents: MagicMock,
undulator: UndulatorInKeV,
):
mock_get_file_contents.return_value = UndulatorEnergyGapLookupTable(
rows=[[0, 10], [10000, 20]]
)
set_mock_value(undulator.gap_access, EnabledDisabledUpper.ENABLED)
await undulator.set(5)

Expand Down
11 changes: 9 additions & 2 deletions tests/devices/util/test_lookup_tables.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import numpy as np
import pytest
from daq_config_server.client import ConfigServer
from daq_config_server.models import UndulatorEnergyGapLookupTable
from pytest import mark

from dodal.devices.util.lookup_tables import (
energy_distance_table,
linear_extrapolation_lut,
linear_interpolation_lut,
parse_lookup_table,
Expand All @@ -21,7 +23,12 @@


async def test_energy_to_distance_table_correct_format():
table = await energy_distance_table(TEST_BEAMLINE_UNDULATOR_TO_GAP_LUT)
config_server = ConfigServer()
table = np.array(
config_server.get_file_contents(
TEST_BEAMLINE_UNDULATOR_TO_GAP_LUT, UndulatorEnergyGapLookupTable
).rows
)
assert table[0][0] == 5700
assert table[49][1] == 6.264
assert table.shape == (50, 2)
Expand Down