From 3558d4e7e6462d4f4a030d7e0cd49525b5c550ce Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Mon, 20 Oct 2025 10:00:49 +0100 Subject: [PATCH 1/6] Initial stab at fixing the issue --- src/dodal/devices/fast_grid_scan.py | 9 ++++- tests/devices/test_gridscan.py | 56 ++++++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/dodal/devices/fast_grid_scan.py b/src/dodal/devices/fast_grid_scan.py index cdee8b035a..24e6b5f2fa 100644 --- a/src/dodal/devices/fast_grid_scan.py +++ b/src/dodal/devices/fast_grid_scan.py @@ -307,10 +307,17 @@ async def prepare(self, value: ParamType): set_statuses = [] LOGGER.info("Applying gridscan parameters...") + # Create arguments for bps.mv for key, signal in self._movable_params.items(): param_value = value.__dict__[key] - set_statuses.append(await set_and_wait_for_value(signal, param_value)) # type: ignore + set_statuses.append( + await set_and_wait_for_value( + signal, + param_value, + # match_value=partial(isclose, param_value, abs_tol=0.0001), + ) + ) # type: ignore # Counter should always start at 0 set_statuses.append(await set_and_wait_for_value(self.position_counter, 0)) diff --git a/tests/devices/test_gridscan.py b/tests/devices/test_gridscan.py index 4e184c0e15..f37c220d8d 100644 --- a/tests/devices/test_gridscan.py +++ b/tests/devices/test_gridscan.py @@ -2,6 +2,7 @@ from asyncio import wait_for from contextlib import nullcontext from dataclasses import dataclass +from typing import Any from unittest.mock import AsyncMock, MagicMock, patch import numpy as np @@ -11,7 +12,11 @@ from bluesky.run_engine import RunEngine from ophyd.status import DeviceStatus, Status from ophyd_async.core import init_devices -from ophyd_async.testing import get_mock_put, set_mock_put_proceeds, set_mock_value +from ophyd_async.testing import ( + get_mock_put, + set_mock_put_proceeds, + set_mock_value, +) from dodal.devices.fast_grid_scan import ( FastGridScanCommon, @@ -474,7 +479,9 @@ async def test_i02_1_gridscan_has_2d_behaviour( async def test_gridscan_prepare_writes_values_and_checks_readback( - grid_scan_devices_with_params_and_valid_state, + grid_scan_devices_with_params_and_valid_state: tuple[ + FastGridScanCommon, GridScanParamsCommon, Any + ], ): grid_scan_device, grid_scan_params, valid_state = ( grid_scan_devices_with_params_and_valid_state @@ -514,7 +521,9 @@ async def test_gridscan_prepare_writes_values_and_checks_readback( async def test_gridscan_prepare_checks_validity_after_writes( - grid_scan_devices_with_params_and_valid_state, + grid_scan_devices_with_params_and_valid_state: tuple[ + FastGridScanCommon, GridScanParamsCommon, Any + ], ): grid_scan_device, grid_scan_params, valid_state = ( grid_scan_devices_with_params_and_valid_state @@ -547,7 +556,9 @@ async def test_gridscan_prepare_checks_validity_after_writes( async def test_gridscan_prepare_times_out_for_validity_check( - grid_scan_devices_with_params_and_valid_state, + grid_scan_devices_with_params_and_valid_state: tuple[ + FastGridScanCommon, GridScanParamsCommon, Any + ], ): grid_scan_device, grid_scan_params, valid_state = ( grid_scan_devices_with_params_and_valid_state @@ -570,3 +581,40 @@ async def test_gridscan_prepare_times_out_for_validity_check( and cause.args[0] == f"{device_name}-scan_invalid didn't match 0.0 in 0.5s, last value 1.0" ) + + +async def test_gridscan_prepare_works_within_tolerance_on_the_readback( + grid_scan_devices_with_params_and_valid_state: tuple[ + FastGridScanCommon, GridScanParamsCommon, Any + ], +): + grid_scan_device, grid_scan_params, valid_state = ( + grid_scan_devices_with_params_and_valid_state + ) + my_event = asyncio.Event() + + async def _wait(*_, **__): + await my_event.wait() + + grid_scan_device.x_step_size = MagicMock() + + # get_mock_put(grid_scan_device.x_step_size).side_effect = _wait + # callback_on_mock_put( + # grid_scan_device.x_step_size, + # lambda *_, **__: set_mock_value( + # grid_scan_device.x_step_size, grid_scan_params.x_step_size_mm + 0.01 + # ), + # ) + # set_mock_put_proceeds(grid_scan_device.x_step_size, False) + + status = grid_scan_device.prepare(grid_scan_params) + + assert not status.done + + set_mock_value( + grid_scan_device.x_step_size, grid_scan_params.x_step_size_mm + ) # + 0.01) + + # set_mock_put_proceeds(grid_scan_device.x_step_size, True) + + await status From b8f80f04b8d7d0be993bdca13225c162e8e260f0 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Fri, 31 Oct 2025 12:52:07 +0000 Subject: [PATCH 2/6] Check that we set fast grid scan params within rounding error --- pyproject.toml | 2 +- src/dodal/devices/fast_grid_scan.py | 8 +++++--- tests/devices/test_gridscan.py | 29 ++++++----------------------- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 105f41f254..ed16bf1547 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dev = [ # Commented out due to dependency version conflict with pydantic 1.x # "copier", "myst-parser", - "ophyd_async[sim]", + "ophyd_async[sim] @ git+https://github.com/bluesky/ophyd-async.git@main", # Needed until https://github.com/bluesky/ophyd-async/pull/1096 is merged "pipdeptree", "pre-commit", "psutil", diff --git a/src/dodal/devices/fast_grid_scan.py b/src/dodal/devices/fast_grid_scan.py index db12695cbd..efd581275f 100644 --- a/src/dodal/devices/fast_grid_scan.py +++ b/src/dodal/devices/fast_grid_scan.py @@ -1,5 +1,7 @@ import asyncio from abc import ABC, abstractmethod +from functools import partial +from math import isclose from typing import Generic, TypeVar import numpy as np @@ -313,11 +315,11 @@ async def prepare(self, value: ParamType): param_value = value.__dict__[key] set_statuses.append( await set_and_wait_for_value( - signal, + signal, # type: ignore param_value, - # match_value=partial(isclose, param_value, abs_tol=0.0001), + match_value=partial(isclose, param_value, abs_tol=0.0001), ) - ) # type: ignore + ) # Counter should always start at 0 set_statuses.append(await set_and_wait_for_value(self.position_counter, 0)) diff --git a/tests/devices/test_gridscan.py b/tests/devices/test_gridscan.py index 0912a9c6cf..c8144b5933 100644 --- a/tests/devices/test_gridscan.py +++ b/tests/devices/test_gridscan.py @@ -13,6 +13,7 @@ from ophyd.status import DeviceStatus, Status from ophyd_async.core import init_devices from ophyd_async.testing import ( + callback_on_mock_put, get_mock_put, set_mock_put_proceeds, set_mock_value, @@ -591,30 +592,12 @@ async def test_gridscan_prepare_works_within_tolerance_on_the_readback( grid_scan_device, grid_scan_params, valid_state = ( grid_scan_devices_with_params_and_valid_state ) - my_event = asyncio.Event() - async def _wait(*_, **__): - await my_event.wait() + grid_scan_params.x_step_size_mm = 0.1111111 - grid_scan_device.x_step_size = MagicMock() + def return_rounded_value(value, *_, **__): + return round(value, 4) - # get_mock_put(grid_scan_device.x_step_size).side_effect = _wait - # callback_on_mock_put( - # grid_scan_device.x_step_size, - # lambda *_, **__: set_mock_value( - # grid_scan_device.x_step_size, grid_scan_params.x_step_size_mm + 0.01 - # ), - # ) - # set_mock_put_proceeds(grid_scan_device.x_step_size, False) + callback_on_mock_put(grid_scan_device.x_step_size, return_rounded_value) - status = grid_scan_device.prepare(grid_scan_params) - - assert not status.done - - set_mock_value( - grid_scan_device.x_step_size, grid_scan_params.x_step_size_mm - ) # + 0.01) - - # set_mock_put_proceeds(grid_scan_device.x_step_size, True) - - await status + await grid_scan_device.prepare(grid_scan_params) From 230bf9c66917229c06da703696382eea0f686334 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Fri, 31 Oct 2025 12:55:28 +0000 Subject: [PATCH 3/6] Update comment for ophyd_async dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ed16bf1547..56a0d0b58b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dev = [ # Commented out due to dependency version conflict with pydantic 1.x # "copier", "myst-parser", - "ophyd_async[sim] @ git+https://github.com/bluesky/ophyd-async.git@main", # Needed until https://github.com/bluesky/ophyd-async/pull/1096 is merged + "ophyd_async[sim] @ git+https://github.com/bluesky/ophyd-async.git@main", # Needed until https://github.com/bluesky/ophyd-async/pull/1096 is released "pipdeptree", "pre-commit", "psutil", From 5c219f0a078dc70b922a8c6ea0bd9871b64166ae Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Tue, 4 Nov 2025 10:46:34 +0000 Subject: [PATCH 4/6] Pin ophyd-async and fix naming issue --- pyproject.toml | 2 +- src/dodal/devices/fast_grid_scan.py | 6 +++++- tests/devices/test_gridscan.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 56a0d0b58b..42e32c1c52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dev = [ # Commented out due to dependency version conflict with pydantic 1.x # "copier", "myst-parser", - "ophyd_async[sim] @ git+https://github.com/bluesky/ophyd-async.git@main", # Needed until https://github.com/bluesky/ophyd-async/pull/1096 is released + "ophyd_async[sim]>=v0.13.6", "pipdeptree", "pre-commit", "psutil", diff --git a/src/dodal/devices/fast_grid_scan.py b/src/dodal/devices/fast_grid_scan.py index efd581275f..f5b554eb43 100644 --- a/src/dodal/devices/fast_grid_scan.py +++ b/src/dodal/devices/fast_grid_scan.py @@ -313,11 +313,15 @@ async def prepare(self, value: ParamType): # Create arguments for bps.mv for key, signal in self._movable_params.items(): param_value = value.__dict__[key] + + matcher = partial(isclose, param_value, abs_tol=0.001) + matcher.__name__ = "does_parameter_match" # Remove when https://github.com/bluesky/ophyd-async/pull/1123 deployed + set_statuses.append( await set_and_wait_for_value( signal, # type: ignore param_value, - match_value=partial(isclose, param_value, abs_tol=0.0001), + match_value=matcher, ) ) diff --git a/tests/devices/test_gridscan.py b/tests/devices/test_gridscan.py index c8144b5933..fd1539fa7a 100644 --- a/tests/devices/test_gridscan.py +++ b/tests/devices/test_gridscan.py @@ -596,7 +596,7 @@ async def test_gridscan_prepare_works_within_tolerance_on_the_readback( grid_scan_params.x_step_size_mm = 0.1111111 def return_rounded_value(value, *_, **__): - return round(value, 4) + return round(value, 3) callback_on_mock_put(grid_scan_device.x_step_size, return_rounded_value) From 6ff992195e0f28bd8266b02b41134376b41f2e0d Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Tue, 4 Nov 2025 11:42:18 +0000 Subject: [PATCH 5/6] Fix linter --- src/dodal/devices/fast_grid_scan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dodal/devices/fast_grid_scan.py b/src/dodal/devices/fast_grid_scan.py index f5b554eb43..b06cb94ad6 100644 --- a/src/dodal/devices/fast_grid_scan.py +++ b/src/dodal/devices/fast_grid_scan.py @@ -315,7 +315,7 @@ async def prepare(self, value: ParamType): param_value = value.__dict__[key] matcher = partial(isclose, param_value, abs_tol=0.001) - matcher.__name__ = "does_parameter_match" # Remove when https://github.com/bluesky/ophyd-async/pull/1123 deployed + matcher.__name__ = "does_parameter_match" # type: ignore # Remove when https://github.com/bluesky/ophyd-async/pull/1123 deployed set_statuses.append( await set_and_wait_for_value( From 1d3bfe4d63a89b2fe13fcd6c749a05fe8f68508b Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Tue, 18 Nov 2025 10:19:11 +0000 Subject: [PATCH 6/6] Make setting the gridscan parameters async --- src/dodal/devices/fast_grid_scan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dodal/devices/fast_grid_scan.py b/src/dodal/devices/fast_grid_scan.py index b06cb94ad6..982ba5673e 100644 --- a/src/dodal/devices/fast_grid_scan.py +++ b/src/dodal/devices/fast_grid_scan.py @@ -318,7 +318,7 @@ async def prepare(self, value: ParamType): matcher.__name__ = "does_parameter_match" # type: ignore # Remove when https://github.com/bluesky/ophyd-async/pull/1123 deployed set_statuses.append( - await set_and_wait_for_value( + set_and_wait_for_value( signal, # type: ignore param_value, match_value=matcher, @@ -326,7 +326,7 @@ async def prepare(self, value: ParamType): ) # Counter should always start at 0 - set_statuses.append(await set_and_wait_for_value(self.position_counter, 0)) + set_statuses.append(set_and_wait_for_value(self.position_counter, 0)) LOGGER.info("Gridscan parameters applied, waiting for sets to complete...")