dls-dodal 1.65.0__py3-none-any.whl → 1.67.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dls_dodal-1.65.0.dist-info → dls_dodal-1.67.0.dist-info}/METADATA +3 -4
- {dls_dodal-1.65.0.dist-info → dls_dodal-1.67.0.dist-info}/RECORD +82 -66
- dodal/_version.py +2 -2
- dodal/beamlines/aithre.py +21 -2
- dodal/beamlines/i03.py +102 -198
- dodal/beamlines/i04.py +40 -4
- dodal/beamlines/i05.py +28 -1
- dodal/beamlines/i06.py +62 -0
- dodal/beamlines/i07.py +20 -0
- dodal/beamlines/i09_1.py +32 -3
- dodal/beamlines/i09_2.py +57 -2
- dodal/beamlines/i10_optics.py +46 -17
- dodal/beamlines/i17.py +7 -3
- dodal/beamlines/i18.py +3 -3
- dodal/beamlines/i19_1.py +26 -14
- dodal/beamlines/i19_2.py +49 -38
- dodal/beamlines/i21.py +2 -2
- dodal/beamlines/i22.py +19 -4
- dodal/beamlines/p38.py +3 -3
- dodal/beamlines/training_rig.py +0 -16
- dodal/cli.py +26 -12
- dodal/common/coordination.py +3 -2
- dodal/device_manager.py +604 -0
- dodal/devices/aithre_lasershaping/goniometer.py +26 -9
- dodal/devices/aperturescatterguard.py +3 -2
- dodal/devices/areadetector/plugins/mjpg.py +10 -3
- dodal/devices/beamsize/__init__.py +0 -0
- dodal/devices/beamsize/beamsize.py +6 -0
- dodal/devices/cryostream.py +28 -57
- dodal/devices/detector/det_resolution.py +4 -2
- dodal/devices/eiger.py +26 -18
- dodal/devices/fast_grid_scan.py +14 -2
- dodal/devices/i03/beamsize.py +35 -0
- dodal/devices/i03/constants.py +7 -0
- dodal/devices/i03/undulator_dcm.py +2 -2
- dodal/devices/i04/beamsize.py +45 -0
- dodal/devices/i04/max_pixel.py +38 -0
- dodal/devices/i04/murko_results.py +36 -26
- dodal/devices/i04/transfocator.py +23 -29
- dodal/devices/i07/id.py +38 -0
- dodal/devices/i09_1_shared/__init__.py +13 -2
- dodal/devices/i09_1_shared/hard_energy.py +112 -0
- dodal/devices/i09_1_shared/hard_undulator_functions.py +85 -21
- dodal/devices/i09_2_shared/__init__.py +0 -0
- dodal/devices/i09_2_shared/i09_apple2.py +86 -0
- dodal/devices/i10/i10_apple2.py +39 -331
- dodal/devices/i17/i17_apple2.py +37 -22
- dodal/devices/i19/access_controlled/attenuator_motor_squad.py +61 -0
- dodal/devices/i19/access_controlled/blueapi_device.py +9 -1
- dodal/devices/i19/access_controlled/shutter.py +2 -4
- dodal/devices/insertion_device/__init__.py +0 -0
- dodal/devices/{apple2_undulator.py → insertion_device/apple2_undulator.py} +122 -69
- dodal/devices/insertion_device/energy_motor_lookup.py +88 -0
- dodal/devices/insertion_device/lookup_table_models.py +287 -0
- dodal/devices/ipin.py +20 -2
- dodal/devices/motors.py +33 -3
- dodal/devices/mx_phase1/beamstop.py +31 -12
- dodal/devices/oav/oav_calculations.py +9 -4
- dodal/devices/oav/oav_detector.py +65 -7
- dodal/devices/oav/oav_parameters.py +3 -1
- dodal/devices/oav/oav_to_redis_forwarder.py +18 -15
- dodal/devices/oav/pin_image_recognition/__init__.py +5 -1
- dodal/devices/oav/pin_image_recognition/utils.py +23 -1
- dodal/devices/oav/snapshots/snapshot_with_grid.py +8 -2
- dodal/devices/oav/utils.py +16 -6
- dodal/devices/robot.py +33 -18
- dodal/devices/scintillator.py +36 -14
- dodal/devices/smargon.py +2 -3
- dodal/devices/thawer.py +7 -45
- dodal/devices/undulator.py +152 -68
- dodal/plans/__init__.py +1 -1
- dodal/plans/configure_arm_trigger_and_disarm_detector.py +2 -4
- dodal/plans/load_panda_yaml.py +9 -0
- dodal/plans/verify_undulator_gap.py +2 -2
- dodal/testing/fixtures/devices/__init__.py +0 -0
- dodal/testing/fixtures/devices/apple2.py +78 -0
- dodal/utils.py +6 -3
- dodal/beamline_specific_utils/i03.py +0 -17
- dodal/testing/__init__.py +0 -3
- dodal/testing/setup.py +0 -67
- {dls_dodal-1.65.0.dist-info → dls_dodal-1.67.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.65.0.dist-info → dls_dodal-1.67.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.65.0.dist-info → dls_dodal-1.67.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.65.0.dist-info → dls_dodal-1.67.0.dist-info}/top_level.txt +0 -0
- /dodal/plans/{scanspec.py → spec_path.py} +0 -0
dodal/devices/i07/id.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from dodal.devices.undulator import UndulatorInKeV, UndulatorOrder
|
|
4
|
+
from dodal.devices.util.lookup_tables import energy_distance_table
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InsertionDevice(UndulatorInKeV):
|
|
8
|
+
"""
|
|
9
|
+
Insertion device for i07 including beamline-specific energy-gap lookup behaviour
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
name: str,
|
|
15
|
+
prefix: str,
|
|
16
|
+
harmonic: UndulatorOrder,
|
|
17
|
+
id_gap_lookup_table_path: str = "/dls_sw/i07/software/gda/config/lookupTables/"
|
|
18
|
+
+ "IIDCalibrationTable.txt",
|
|
19
|
+
) -> None:
|
|
20
|
+
super().__init__(prefix, id_gap_lookup_table_path, name=name)
|
|
21
|
+
self.harmonic = harmonic
|
|
22
|
+
|
|
23
|
+
async def _get_gap_to_match_energy(self, energy_kev: float) -> float:
|
|
24
|
+
"""
|
|
25
|
+
i07's energy scans remain on a particular harmonic while changing energy. The
|
|
26
|
+
calibration table has one row for each harmonic, row contains max and min
|
|
27
|
+
energies and their corresponding ID gaps. The requested energy is used to
|
|
28
|
+
interpolate between these values, assuming a linear relationship on the relevant
|
|
29
|
+
scale.
|
|
30
|
+
"""
|
|
31
|
+
energy_to_distance_table: np.ndarray = await energy_distance_table(
|
|
32
|
+
self.id_gap_lookup_table_path, comments="#", skiprows=2
|
|
33
|
+
)
|
|
34
|
+
harmonic_value: int = await self.harmonic.value.get_value()
|
|
35
|
+
|
|
36
|
+
row: np.ndarray = energy_to_distance_table[harmonic_value - 1, :]
|
|
37
|
+
gap = np.interp(energy_kev, [row[1], row[2]], [row[3], row[4]])
|
|
38
|
+
return gap
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .hard_energy import HardEnergy, HardInsertionDeviceEnergy
|
|
2
|
+
from .hard_undulator_functions import (
|
|
3
|
+
calculate_energy_i09_hu,
|
|
4
|
+
calculate_gap_i09_hu,
|
|
5
|
+
get_hu_lut_as_dict,
|
|
6
|
+
)
|
|
2
7
|
|
|
3
|
-
__all__ = [
|
|
8
|
+
__all__ = [
|
|
9
|
+
"calculate_gap_i09_hu",
|
|
10
|
+
"get_hu_lut_as_dict",
|
|
11
|
+
"calculate_energy_i09_hu",
|
|
12
|
+
"HardInsertionDeviceEnergy",
|
|
13
|
+
"HardEnergy",
|
|
14
|
+
]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from asyncio import gather
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
|
|
4
|
+
from bluesky.protocols import Locatable, Location, Movable
|
|
5
|
+
from numpy import ndarray
|
|
6
|
+
from ophyd_async.core import (
|
|
7
|
+
AsyncStatus,
|
|
8
|
+
Reference,
|
|
9
|
+
StandardReadable,
|
|
10
|
+
StandardReadableFormat,
|
|
11
|
+
derived_signal_rw,
|
|
12
|
+
soft_signal_rw,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from dodal.devices.common_dcm import DoubleCrystalMonochromatorBase
|
|
16
|
+
from dodal.devices.i09_1_shared.hard_undulator_functions import (
|
|
17
|
+
MAX_ENERGY_COLUMN,
|
|
18
|
+
MIN_ENERGY_COLUMN,
|
|
19
|
+
)
|
|
20
|
+
from dodal.devices.undulator import UndulatorInMm, UndulatorOrder
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HardInsertionDeviceEnergy(StandardReadable, Movable[float]):
|
|
24
|
+
"""
|
|
25
|
+
Compound device to link hard x-ray undulator gap and order to photon energy.
|
|
26
|
+
Setting the energy adjusts the undulator gap accordingly.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
undulator_order: UndulatorOrder,
|
|
32
|
+
undulator: UndulatorInMm,
|
|
33
|
+
lut: dict[int, ndarray],
|
|
34
|
+
gap_to_energy_func: Callable[..., float],
|
|
35
|
+
energy_to_gap_func: Callable[..., float],
|
|
36
|
+
name: str = "",
|
|
37
|
+
) -> None:
|
|
38
|
+
self._lut = lut
|
|
39
|
+
self.gap_to_energy_func = gap_to_energy_func
|
|
40
|
+
self.energy_to_gap_func = energy_to_gap_func
|
|
41
|
+
self._undulator_order_ref = Reference(undulator_order)
|
|
42
|
+
self._undulator_ref = Reference(undulator)
|
|
43
|
+
|
|
44
|
+
self.add_readables([undulator_order, undulator.current_gap])
|
|
45
|
+
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
46
|
+
self.energy_demand = soft_signal_rw(float)
|
|
47
|
+
self.energy = derived_signal_rw(
|
|
48
|
+
raw_to_derived=self._read_energy,
|
|
49
|
+
set_derived=self._set_energy,
|
|
50
|
+
current_gap=self._undulator_ref().gap_motor.user_readback,
|
|
51
|
+
current_order=self._undulator_order_ref().value,
|
|
52
|
+
derived_units="keV",
|
|
53
|
+
)
|
|
54
|
+
super().__init__(name=name)
|
|
55
|
+
|
|
56
|
+
def _read_energy(self, current_gap: float, current_order: int) -> float:
|
|
57
|
+
return self.gap_to_energy_func(
|
|
58
|
+
gap=current_gap,
|
|
59
|
+
look_up_table=self._lut,
|
|
60
|
+
order=current_order,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
async def _set_energy(self, energy: float) -> None:
|
|
64
|
+
current_order = await self._undulator_order_ref().value.get_value()
|
|
65
|
+
min_energy, max_energy = self._lut[current_order][
|
|
66
|
+
MIN_ENERGY_COLUMN : MAX_ENERGY_COLUMN + 1
|
|
67
|
+
]
|
|
68
|
+
if not (min_energy <= energy <= max_energy):
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"Requested energy {energy} keV is out of range for harmonic {current_order}: "
|
|
71
|
+
f"[{min_energy}, {max_energy}] keV"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
target_gap = self.energy_to_gap_func(
|
|
75
|
+
photon_energy_kev=energy, look_up_table=self._lut, order=current_order
|
|
76
|
+
)
|
|
77
|
+
await self._undulator_ref().set(target_gap)
|
|
78
|
+
|
|
79
|
+
@AsyncStatus.wrap
|
|
80
|
+
async def set(self, value: float) -> None:
|
|
81
|
+
self.energy_demand.set(value)
|
|
82
|
+
await self.energy.set(value)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class HardEnergy(StandardReadable, Locatable[float]):
|
|
86
|
+
"""
|
|
87
|
+
Energy compound device that provides combined change of both DCM energy and undulator gap accordingly.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
dcm: DoubleCrystalMonochromatorBase,
|
|
93
|
+
undulator_energy: HardInsertionDeviceEnergy,
|
|
94
|
+
name: str = "",
|
|
95
|
+
) -> None:
|
|
96
|
+
self._dcm_ref = Reference(dcm)
|
|
97
|
+
self._undulator_energy_ref = Reference(undulator_energy)
|
|
98
|
+
self.add_readables([undulator_energy, dcm.energy_in_keV])
|
|
99
|
+
super().__init__(name=name)
|
|
100
|
+
|
|
101
|
+
@AsyncStatus.wrap
|
|
102
|
+
async def set(self, value: float) -> None:
|
|
103
|
+
await gather(
|
|
104
|
+
self._dcm_ref().energy_in_keV.set(value),
|
|
105
|
+
self._undulator_energy_ref().set(value),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
async def locate(self) -> Location[float]:
|
|
109
|
+
return Location(
|
|
110
|
+
setpoint=await self._dcm_ref().energy_in_keV.user_setpoint.get_value(),
|
|
111
|
+
readback=await self._dcm_ref().energy_in_keV.user_readback.get_value(),
|
|
112
|
+
)
|
|
@@ -14,11 +14,16 @@ RING_ENERGY_COLUMN = 1
|
|
|
14
14
|
MAGNET_FIELD_COLUMN = 2
|
|
15
15
|
MIN_ENERGY_COLUMN = 3
|
|
16
16
|
MAX_ENERGY_COLUMN = 4
|
|
17
|
+
MIN_GAP_COLUMN = 5
|
|
18
|
+
MAX_GAP_COLUMN = 6
|
|
17
19
|
GAP_OFFSET_COLUMN = 7
|
|
18
20
|
|
|
21
|
+
MAGNET_BLOCKS_PER_PERIOD = 4
|
|
22
|
+
MAGNTE_BLOCK_HEIGHT_MM = 16
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
|
|
25
|
+
async def get_hu_lut_as_dict(lut_path: str) -> dict[int, np.ndarray]:
|
|
26
|
+
lut_dict: dict[int, np.ndarray] = {}
|
|
22
27
|
_lookup_table: np.ndarray = await energy_distance_table(
|
|
23
28
|
lut_path,
|
|
24
29
|
comments=LUT_COMMENTS,
|
|
@@ -26,13 +31,44 @@ async def get_hu_lut_as_dict(lut_path: str) -> dict:
|
|
|
26
31
|
)
|
|
27
32
|
for i in range(_lookup_table.shape[0]):
|
|
28
33
|
lut_dict[_lookup_table[i][0]] = _lookup_table[i]
|
|
29
|
-
|
|
34
|
+
LOGGER.debug(f"Loaded lookup table: {lut_dict}")
|
|
30
35
|
return lut_dict
|
|
31
36
|
|
|
32
37
|
|
|
38
|
+
def _validate_order(order: int, look_up_table: dict[int, "np.ndarray"]) -> None:
|
|
39
|
+
"""Validate that the harmonic order exists in the lookup table."""
|
|
40
|
+
if order not in look_up_table.keys():
|
|
41
|
+
raise ValueError(f"Order parameter {order} not found in lookup table")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _calculate_gamma(look_up_table: dict[int, "np.ndarray"], order: int) -> float:
|
|
45
|
+
"""Calculate the Lorentz factor gamma from the lookup table."""
|
|
46
|
+
return 1000 * look_up_table[order][RING_ENERGY_COLUMN] / ELECTRON_REST_ENERGY_MEV
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _calculate_undulator_parameter_max(
|
|
50
|
+
magnet_field: float, undulator_period_mm: int
|
|
51
|
+
) -> float:
|
|
52
|
+
"""
|
|
53
|
+
Calculate the maximum undulator parameter.
|
|
54
|
+
"""
|
|
55
|
+
return (
|
|
56
|
+
(
|
|
57
|
+
2
|
|
58
|
+
* 0.0934
|
|
59
|
+
* undulator_period_mm
|
|
60
|
+
* magnet_field
|
|
61
|
+
* MAGNET_BLOCKS_PER_PERIOD
|
|
62
|
+
/ np.pi
|
|
63
|
+
)
|
|
64
|
+
* np.sin(np.pi / MAGNET_BLOCKS_PER_PERIOD)
|
|
65
|
+
* (1 - np.exp(-2 * np.pi * MAGNTE_BLOCK_HEIGHT_MM / undulator_period_mm))
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
33
69
|
def calculate_gap_i09_hu(
|
|
34
70
|
photon_energy_kev: float,
|
|
35
|
-
look_up_table: dict[int,
|
|
71
|
+
look_up_table: dict[int, np.ndarray],
|
|
36
72
|
order: int = 1,
|
|
37
73
|
gap_offset: float = 0.0,
|
|
38
74
|
undulator_period_mm: int = 27,
|
|
@@ -52,13 +88,9 @@ def calculate_gap_i09_hu(
|
|
|
52
88
|
Returns:
|
|
53
89
|
float: Calculated undulator gap in millimeters.
|
|
54
90
|
"""
|
|
55
|
-
magnet_blocks_per_period = 4
|
|
56
|
-
magnet_block_height_mm = 16
|
|
57
91
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
gamma = 1000 * look_up_table[order][RING_ENERGY_COLUMN] / ELECTRON_REST_ENERGY_MEV
|
|
92
|
+
_validate_order(order, look_up_table)
|
|
93
|
+
gamma = _calculate_gamma(look_up_table, order)
|
|
62
94
|
|
|
63
95
|
# Constructive interference of radiation emitted at different poles
|
|
64
96
|
# lamda = (lambda_u/2*gamma^2)*(1+K^2/2 + gamma^2*theta^2)/n for n=1,2,3...
|
|
@@ -83,17 +115,8 @@ def calculate_gap_i09_hu(
|
|
|
83
115
|
# leading to K = 0.934*B0[T]*lambda_u[cm]*exp(-pi*gap/lambda_u) or
|
|
84
116
|
# K = undulator_parameter_max*exp(-pi*gap/lambda_u)
|
|
85
117
|
# Calculating undulator_parameter_max gives:
|
|
86
|
-
undulator_parameter_max = (
|
|
87
|
-
|
|
88
|
-
2
|
|
89
|
-
* 0.0934
|
|
90
|
-
* undulator_period_mm
|
|
91
|
-
* look_up_table[order][MAGNET_FIELD_COLUMN]
|
|
92
|
-
* magnet_blocks_per_period
|
|
93
|
-
/ np.pi
|
|
94
|
-
)
|
|
95
|
-
* np.sin(np.pi / magnet_blocks_per_period)
|
|
96
|
-
* (1 - np.exp(-2 * np.pi * magnet_block_height_mm / undulator_period_mm))
|
|
118
|
+
undulator_parameter_max = _calculate_undulator_parameter_max(
|
|
119
|
+
look_up_table[order][MAGNET_FIELD_COLUMN], undulator_period_mm
|
|
97
120
|
)
|
|
98
121
|
|
|
99
122
|
# Finnaly, rearranging the equation:
|
|
@@ -109,3 +132,44 @@ def calculate_gap_i09_hu(
|
|
|
109
132
|
)
|
|
110
133
|
|
|
111
134
|
return gap
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def calculate_energy_i09_hu(
|
|
138
|
+
gap: float,
|
|
139
|
+
look_up_table: dict[int, "np.ndarray"],
|
|
140
|
+
order: int = 1,
|
|
141
|
+
gap_offset: float = 0.0,
|
|
142
|
+
undulator_period_mm: int = 27,
|
|
143
|
+
) -> float:
|
|
144
|
+
"""
|
|
145
|
+
Calculate the photon energy produced by the undulator at a given gap and harmonic order.
|
|
146
|
+
Reverse of the calculate_gap_i09_hu function.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
gap (float): Undulator gap in millimeters.
|
|
150
|
+
look_up_table (dict[int, np.ndarray]): Lookup table containing undulator and beamline parameters for each harmonic order.
|
|
151
|
+
order (int, optional): Harmonic order for which to calculate the energy. Defaults to 1.
|
|
152
|
+
gap_offset (float, optional): Additional gap offset to apply (in mm). Defaults to 0.0.
|
|
153
|
+
undulator_period_mm (int, optional): Undulator period in mm. Defaults to 27.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
float: Calculated photon energy in keV.
|
|
157
|
+
"""
|
|
158
|
+
_validate_order(order, look_up_table)
|
|
159
|
+
|
|
160
|
+
gamma = _calculate_gamma(look_up_table, order)
|
|
161
|
+
undulator_parameter_max = _calculate_undulator_parameter_max(
|
|
162
|
+
look_up_table[order][MAGNET_FIELD_COLUMN], undulator_period_mm
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
undulator_parameter = undulator_parameter_max / np.exp(
|
|
166
|
+
(gap - look_up_table[order][GAP_OFFSET_COLUMN] - gap_offset)
|
|
167
|
+
/ (undulator_period_mm / np.pi)
|
|
168
|
+
)
|
|
169
|
+
energy_kev = (
|
|
170
|
+
4.959368e-6
|
|
171
|
+
* order
|
|
172
|
+
* np.square(gamma)
|
|
173
|
+
/ (undulator_period_mm * (np.square(undulator_parameter) + 2))
|
|
174
|
+
)
|
|
175
|
+
return energy_kev
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from dodal.devices.insertion_device.apple2_undulator import (
|
|
2
|
+
MAXIMUM_MOVE_TIME,
|
|
3
|
+
Apple2,
|
|
4
|
+
Apple2Controller,
|
|
5
|
+
Apple2PhasesVal,
|
|
6
|
+
Apple2Val,
|
|
7
|
+
Pol,
|
|
8
|
+
UndulatorPhaseAxes,
|
|
9
|
+
)
|
|
10
|
+
from dodal.devices.insertion_device.energy_motor_lookup import EnergyMotorLookup
|
|
11
|
+
|
|
12
|
+
J09_GAP_POLY_DEG_COLUMNS = [
|
|
13
|
+
"9th-order",
|
|
14
|
+
"8th-order",
|
|
15
|
+
"7th-order",
|
|
16
|
+
"6th-order",
|
|
17
|
+
"5th-order",
|
|
18
|
+
"4th-order",
|
|
19
|
+
"3rd-order",
|
|
20
|
+
"2nd-order",
|
|
21
|
+
"1st-order",
|
|
22
|
+
"0th-order",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
J09_PHASE_POLY_DEG_COLUMNS = ["0th-order"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class J09Apple2Controller(Apple2Controller[Apple2[UndulatorPhaseAxes]]):
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
apple2: Apple2[UndulatorPhaseAxes],
|
|
32
|
+
gap_energy_motor_lut: EnergyMotorLookup,
|
|
33
|
+
phase_energy_motor_lut: EnergyMotorLookup,
|
|
34
|
+
units: str = "keV",
|
|
35
|
+
name: str = "",
|
|
36
|
+
) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Parameters:
|
|
39
|
+
-----------
|
|
40
|
+
apple2 : Apple2
|
|
41
|
+
An Apple2 device.
|
|
42
|
+
gap_energy_motor_lut: EnergyMotorLookup
|
|
43
|
+
The class that handles the gap look up table logic for the insertion device.
|
|
44
|
+
phase_energy_motor_lut: EnergyMotorLookup
|
|
45
|
+
The class that handles the phase look up table logic for the insertion device.
|
|
46
|
+
units:
|
|
47
|
+
the units of this device. Defaults to eV.
|
|
48
|
+
name : str, optional
|
|
49
|
+
New device name.
|
|
50
|
+
"""
|
|
51
|
+
self.gap_energy_motor_lut = gap_energy_motor_lut
|
|
52
|
+
self.phase_energy_motor_lut = phase_energy_motor_lut
|
|
53
|
+
super().__init__(
|
|
54
|
+
apple2=apple2,
|
|
55
|
+
gap_energy_motor_converter=gap_energy_motor_lut.find_value_in_lookup_table,
|
|
56
|
+
phase_energy_motor_converter=phase_energy_motor_lut.find_value_in_lookup_table,
|
|
57
|
+
units=units,
|
|
58
|
+
name=name,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def _get_apple2_value(self, gap: float, phase: float, pol: Pol) -> Apple2Val:
|
|
62
|
+
return Apple2Val(
|
|
63
|
+
gap=f"{gap:.6f}",
|
|
64
|
+
phase=Apple2PhasesVal(
|
|
65
|
+
top_outer=f"{phase:.6f}",
|
|
66
|
+
top_inner=f"{0.0:.6f}",
|
|
67
|
+
btm_inner=f"{phase:.6f}",
|
|
68
|
+
btm_outer=f"{0.0:.6f}",
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async def _set_pol(
|
|
73
|
+
self,
|
|
74
|
+
value: Pol,
|
|
75
|
+
) -> None:
|
|
76
|
+
# I09 require all palarisation change to go via LH.
|
|
77
|
+
target_energy = await self.energy.get_value()
|
|
78
|
+
if value is not Pol.LH:
|
|
79
|
+
self._polarisation_setpoint_set(Pol.LH)
|
|
80
|
+
max_lh_energy = self.gap_energy_motor_lut.lut.root[Pol.LH].max_energy
|
|
81
|
+
lh_setpoint = (
|
|
82
|
+
max_lh_energy if target_energy > max_lh_energy else target_energy
|
|
83
|
+
)
|
|
84
|
+
await self.energy.set(lh_setpoint, timeout=MAXIMUM_MOVE_TIME)
|
|
85
|
+
self._polarisation_setpoint_set(value)
|
|
86
|
+
await self.energy.set(target_energy, timeout=MAXIMUM_MOVE_TIME)
|