1010
1111
1212@dataclass
13- class AxesTestConfig :
13+ class RotatedCartesianFrameTestConfig :
1414 i_read : SignalR [float ]
1515 j_read : SignalR [float ]
1616 i_write : Movable [float ]
@@ -22,68 +22,118 @@ class AxesTestConfig:
2222 j_rotation_axis : SignalRW [float ]
2323
2424
25- async def assert_set_axis_preserves_other (
26- * ,
25+ async def _set_initial_state (
2726 i_val : float ,
2827 j_val : float ,
2928 angle_deg_val : float ,
30- new_i_axis_value : float ,
31- new_j_axis_value : float ,
32- axes_config : AxesTestConfig ,
29+ frame_config : RotatedCartesianFrameTestConfig ,
3330) -> None :
3431 await asyncio .gather (
35- maybe_await (axes_config .i_write .set (i_val )),
36- maybe_await (axes_config .j_write .set (j_val )),
32+ maybe_await (frame_config .i_write .set (i_val )),
33+ maybe_await (frame_config .j_write .set (j_val )),
3734 )
38- angle_deg = axes_config .angle_deg
39- if isinstance (angle_deg , Movable ):
40- await maybe_await (angle_deg .set (angle_deg_val ))
41-
42- theta = radians (angle_deg_val )
35+ if isinstance (frame_config .angle_deg , Movable ):
36+ await maybe_await (frame_config .angle_deg .set (angle_deg_val ))
4337
44- # Capture invariant component before move
45- expected_i = new_i_axis_value
46- expected_j = axes_config .expected_j_read_func (i_val , j_val , theta )
4738
48- # Move axis to new position
49- await axes_config .i_rotation_axis .set (expected_i )
50-
51- # Read back motors
39+ async def _read_rotated_components (
40+ frame_config : RotatedCartesianFrameTestConfig , theta : float
41+ ) -> tuple [float , float ]:
5242 i_new , j_new = await asyncio .gather (
53- axes_config .i_read .get_value (), axes_config .j_read .get_value ()
43+ frame_config .i_read .get_value (),
44+ frame_config .j_read .get_value (),
5445 )
55-
56- # Compute rotated components after move
57- i_val_after = axes_config .expected_i_read_func (i_new , j_new , theta )
58- j_val_after = axes_config .expected_j_read_func (i_new , j_new , theta )
59-
60- assert i_val_after == pytest .approx (expected_i )
61- assert j_val_after == pytest .approx (expected_j )
62-
63- await asyncio .gather (
64- maybe_await (axes_config .i_write .set (i_val )),
65- maybe_await (axes_config .j_write .set (j_val )),
46+ return (
47+ frame_config .expected_i_read_func (i_new , j_new , theta ),
48+ frame_config .expected_j_read_func (i_new , j_new , theta ),
6649 )
67- angle_deg = axes_config .angle_deg
68- if isinstance (angle_deg , Movable ):
69- await maybe_await (angle_deg .set (angle_deg_val ))
70-
71- theta = radians (angle_deg_val )
72-
73- expected_j = new_j_axis_value
74- expected_i = axes_config .expected_i_read_func (i_val , j_val , theta )
7550
76- # Move axis to new position
77- await axes_config .j_rotation_axis .set (expected_j )
7851
79- # Read back motors
80- i_new , j_new = await asyncio .gather (
81- axes_config .i_read .get_value (), axes_config .j_read .get_value ()
52+ async def _assert_setting_axis_preserves_other (
53+ set_axis : SignalRW [float ],
54+ set_value : float ,
55+ expected_i : float ,
56+ expected_j : float ,
57+ frame_config : RotatedCartesianFrameTestConfig ,
58+ theta : float ,
59+ ) -> None :
60+ await set_axis .set (set_value )
61+ i_after , j_after = await _read_rotated_components (
62+ frame_config = frame_config , theta = theta
8263 )
64+ assert i_after == pytest .approx (expected_i )
65+ assert j_after == pytest .approx (expected_j )
8366
84- # Compute rotated components after move
85- i_val_after = axes_config .expected_i_read_func (i_new , j_new , theta )
86- j_val_after = axes_config .expected_j_read_func (i_new , j_new , theta )
8767
88- assert i_val_after == pytest .approx (expected_i )
89- assert j_val_after == pytest .approx (expected_j )
68+ async def assert_rotated_axes_are_orthogonal (
69+ i_val : float ,
70+ j_val : float ,
71+ angle_deg_val : float ,
72+ new_i_axis_value : float ,
73+ new_j_axis_value : float ,
74+ frame_config : RotatedCartesianFrameTestConfig ,
75+ ) -> None :
76+ """Assert that the virtual rotated axes behave as independent Cartesian
77+ coordinates.
78+
79+ This helper verifies that setting one virtual axis in a rotated frame
80+ updates only that axis while preserving the orthogonal axis in the same
81+ rotated frame.
82+
83+ Specifically, the test:
84+ - Initializes the underlying i/j motors and rotation angle.
85+ - Sets the virtual i' axis and asserts that:
86+ * i' moves to the requested value
87+ * j' remains unchanged
88+ - Resets the system.
89+ - Sets the virtual j' axis and asserts that:
90+ * j' moves to the requested value
91+ * i' remains unchanged
92+
93+ This confirms that the virtual axes form a proper orthogonal Cartesian
94+ coordinate frame, and that inverse rotations are applied correctly when
95+ writing to the underlying motors.
96+
97+ Args:
98+ i_val: Initial value for the underlying i-axis.
99+ j_val: Initial value for the underlying j-axis.
100+ angle_deg_val: Rotation angle (in degrees) defining the rotated frame.
101+ new_i_axis_value: Target value for the virtual i' axis.
102+ new_j_axis_value: Target value for the virtual j' axis.
103+ frame_config: Configuration describing how virtual rotated axes are derived from
104+ the underlying motors, including expected readout functions.
105+ """
106+ # Test setting i′ preserves j′
107+ await _set_initial_state (
108+ i_val = i_val ,
109+ j_val = j_val ,
110+ angle_deg_val = angle_deg_val ,
111+ frame_config = frame_config ,
112+ )
113+ theta = radians (angle_deg_val )
114+ expected_j = frame_config .expected_j_read_func (i_val , j_val , theta )
115+
116+ await _assert_setting_axis_preserves_other (
117+ set_axis = frame_config .i_rotation_axis ,
118+ set_value = new_i_axis_value ,
119+ expected_i = new_i_axis_value ,
120+ expected_j = expected_j ,
121+ frame_config = frame_config ,
122+ theta = theta ,
123+ )
124+ # Test setting j′ preserves i′
125+ await _set_initial_state (
126+ i_val = i_val ,
127+ j_val = j_val ,
128+ angle_deg_val = angle_deg_val ,
129+ frame_config = frame_config ,
130+ )
131+ expected_i = frame_config .expected_i_read_func (i_val , j_val , theta )
132+ await _assert_setting_axis_preserves_other (
133+ set_axis = frame_config .j_rotation_axis ,
134+ set_value = new_j_axis_value ,
135+ expected_i = expected_i ,
136+ expected_j = new_j_axis_value ,
137+ frame_config = frame_config ,
138+ theta = theta ,
139+ )
0 commit comments