Skip to content

Commit 1deaf91

Browse files
i23 Laser shaping: Goniometer vertical signal (#1164)
* Add logic for vertical sample position to goniometer * Change read signals to locatables in derived signal * Fix PVs used in derived signal * Attempt fixing trigonometry of get function * Revert "Attempt fixing trigonometry of get function" This reverts commit 2a45ebc. * Add omega to set function * Appease codecov * Lint * Overhaul tests * Expand doc string to explain signals * Add test for get method
1 parent 33b9815 commit 1deaf91

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed
Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
from ophyd_async.core import StandardReadable
1+
import asyncio
2+
import math
3+
4+
from ophyd_async.core import StandardReadable, derived_signal_rw
25
from ophyd_async.epics.motor import Motor
36

47

58
class Goniometer(StandardReadable):
6-
"""Goniometer and the stages it sits on"""
9+
"""The Aithre lab goniometer and the XYZ stage it sits on.
10+
11+
`x`, `y` and `z` control the axes of the positioner at the base, while `sampy` and
12+
`sampz` control the positioner of the sample. `omega` is the rotation about the
13+
x-axis (along the length of the sample holder).
14+
15+
The `vertical_position` signal refers to the height of the sample from the point of
16+
view of the OAV and setting this value moves the sample vertically in the OAV plane
17+
regardless of the current rotation.
18+
"""
719

820
def __init__(self, prefix: str, name: str = "") -> None:
921
self.x = Motor(prefix + "X")
@@ -12,4 +24,26 @@ def __init__(self, prefix: str, name: str = "") -> None:
1224
self.sampy = Motor(prefix + "SAMPY")
1325
self.sampz = Motor(prefix + "SAMPZ")
1426
self.omega = Motor(prefix + "OMEGA")
27+
self.vertical_position = derived_signal_rw(
28+
self._get,
29+
self._set,
30+
sampy=self.sampy,
31+
sampz=self.sampz,
32+
omega=self.omega,
33+
)
1534
super().__init__(name)
35+
36+
def _get(self, sampz: float, sampy: float, omega: float) -> float:
37+
z_component = sampz * math.cos(math.radians(omega))
38+
y_component = sampy * math.sin(math.radians(omega))
39+
return z_component + y_component
40+
41+
async def _set(self, value: float) -> None:
42+
omega = await self.omega.user_readback.get_value()
43+
z_component = value * math.cos(math.radians(omega))
44+
y_component = value * math.sin(math.radians(omega))
45+
await asyncio.gather(
46+
self.sampy.set(y_component),
47+
self.sampz.set(z_component),
48+
self.omega.set(omega),
49+
)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import math
2+
3+
import pytest
4+
from bluesky.run_engine import RunEngine
5+
from ophyd_async.core import init_devices
6+
7+
from dodal.beamlines import aithre
8+
from dodal.devices.aithre_lasershaping.goniometer import Goniometer
9+
from dodal.devices.util.test_utils import patch_motor
10+
11+
12+
@pytest.fixture
13+
def goniometer(RE: RunEngine) -> Goniometer:
14+
with init_devices(mock=True):
15+
gonio = aithre.goniometer(connect_immediately=True, mock=True)
16+
patch_motor(gonio.omega)
17+
patch_motor(gonio.sampy)
18+
patch_motor(gonio.sampz)
19+
return gonio
20+
21+
22+
@pytest.mark.parametrize(
23+
"vertical_set_value, omega_set_value, expected_horz, expected_vert",
24+
[
25+
[2, 60, 1, math.sqrt(3)],
26+
[-10, 390, -5 * math.sqrt(3), -5],
27+
[0.5, -135, -math.sqrt(2) / 4, -math.sqrt(2) / 4],
28+
[1, 0, 1, 0],
29+
[-1.5, 90, 0, -1.5],
30+
],
31+
)
32+
async def test_vertical_signal_set(
33+
goniometer: Goniometer,
34+
vertical_set_value: float,
35+
omega_set_value: float,
36+
expected_horz: float,
37+
expected_vert: float,
38+
):
39+
await goniometer.omega.set(omega_set_value)
40+
await goniometer.vertical_position.set(vertical_set_value)
41+
42+
assert await goniometer.sampz.user_readback.get_value() == pytest.approx(
43+
expected_horz
44+
)
45+
assert await goniometer.sampy.user_readback.get_value() == pytest.approx(
46+
expected_vert
47+
)
48+
49+
await goniometer.vertical_position.set(vertical_set_value * 2)
50+
assert await goniometer.sampz.user_readback.get_value() == pytest.approx(
51+
expected_horz * 2
52+
)
53+
assert await goniometer.sampy.user_readback.get_value() == pytest.approx(
54+
expected_vert * 2
55+
)
56+
57+
58+
@pytest.mark.parametrize("set_value", [-5, 2.7, 0])
59+
async def test_vertical_position_get(goniometer: Goniometer, set_value: float):
60+
await goniometer.vertical_position.set(set_value)
61+
assert await goniometer.vertical_position.get_value() == set_value

0 commit comments

Comments
 (0)