dls-dodal 1.69.0__py3-none-any.whl → 2.0.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.69.0.dist-info → dls_dodal-2.0.0.dist-info}/METADATA +1 -1
- dls_dodal-2.0.0.dist-info/RECORD +354 -0
- {dls_dodal-1.69.0.dist-info → dls_dodal-2.0.0.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +10 -17
- dodal/beamlines/adsim.py +17 -17
- dodal/beamlines/b01_1.py +11 -0
- dodal/beamlines/b07.py +17 -21
- dodal/beamlines/b07_1.py +20 -22
- dodal/beamlines/b07_shared.py +12 -0
- dodal/beamlines/b16.py +1 -1
- dodal/beamlines/b21.py +15 -6
- dodal/beamlines/i02_1.py +3 -3
- dodal/beamlines/i02_2.py +1 -1
- dodal/beamlines/i03.py +4 -4
- dodal/beamlines/i04.py +16 -8
- dodal/beamlines/i05.py +7 -45
- dodal/beamlines/i05_1.py +4 -13
- dodal/beamlines/i05_shared.py +51 -0
- dodal/beamlines/i06_1.py +7 -5
- dodal/beamlines/{i06.py → i06_shared.py} +25 -14
- dodal/beamlines/i07.py +14 -16
- dodal/beamlines/i09.py +54 -51
- dodal/beamlines/i09_1.py +25 -64
- dodal/beamlines/i09_1_shared.py +61 -0
- dodal/beamlines/i09_2.py +6 -100
- dodal/beamlines/i09_2_shared.py +110 -0
- dodal/beamlines/i10.py +60 -54
- dodal/beamlines/i10_1.py +99 -10
- dodal/beamlines/{i10_optics.py → i10_shared.py} +80 -66
- dodal/beamlines/i11.py +31 -18
- dodal/beamlines/i13_1.py +1 -1
- dodal/beamlines/i15.py +6 -6
- dodal/beamlines/i15_1.py +6 -6
- dodal/beamlines/i17.py +37 -28
- dodal/beamlines/i18.py +3 -4
- dodal/beamlines/i19_1.py +95 -34
- dodal/beamlines/i19_2.py +68 -52
- dodal/beamlines/i19_optics.py +26 -13
- dodal/beamlines/i20_1.py +6 -14
- dodal/beamlines/i21.py +35 -28
- dodal/beamlines/i22.py +19 -4
- dodal/beamlines/i23.py +1 -2
- dodal/beamlines/i24.py +11 -10
- dodal/beamlines/k07.py +99 -5
- dodal/beamlines/p38.py +3 -3
- dodal/beamlines/p60.py +28 -17
- dodal/beamlines/p99.py +16 -15
- dodal/beamlines/training_rig.py +20 -12
- dodal/cli.py +36 -2
- dodal/common/beamlines/beamline_parameters.py +2 -1
- dodal/common/beamlines/beamline_utils.py +11 -9
- dodal/common/beamlines/commissioning_mode.py +6 -3
- dodal/common/coordination.py +12 -14
- dodal/common/crystal_metadata.py +5 -8
- dodal/common/device_utils.py +4 -3
- dodal/common/maths.py +28 -40
- dodal/common/udc_directory_provider.py +13 -8
- dodal/common/visit.py +18 -21
- dodal/common/watcher_utils.py +13 -12
- dodal/device_manager.py +94 -54
- dodal/devices/aperturescatterguard.py +26 -27
- dodal/devices/areadetector/plugins/cam.py +1 -3
- dodal/devices/areadetector/plugins/mjpg.py +6 -5
- dodal/devices/attenuator/attenuator.py +12 -11
- dodal/devices/beamlines/b07/__init__.py +3 -0
- dodal/devices/{b07_1 → beamlines/b07_1}/__init__.py +2 -2
- dodal/devices/{b07_1 → beamlines/b07_1}/ccmc.py +5 -10
- dodal/devices/{b16 → beamlines/b16}/detector.py +2 -3
- dodal/devices/{i02_1 → beamlines/i02_1}/fast_grid_scan.py +2 -3
- dodal/devices/{i02_1 → beamlines/i02_1}/sample_motors.py +1 -1
- dodal/devices/{i03 → beamlines/i03}/beamsize.py +11 -7
- dodal/devices/{i03 → beamlines/i03}/dcm.py +1 -2
- dodal/devices/{i03 → beamlines/i03}/undulator_dcm.py +4 -5
- dodal/devices/beamlines/i04/beam_centre.py +151 -0
- dodal/devices/{i04 → beamlines/i04}/beamsize.py +11 -7
- dodal/devices/{i04 → beamlines/i04}/murko_results.py +5 -5
- dodal/devices/{i04 → beamlines/i04}/transfocator.py +10 -15
- dodal/devices/beamlines/i05/__init__.py +3 -0
- dodal/devices/beamlines/i06_shared/__init__.py +3 -0
- dodal/devices/beamlines/i06_shared/i06_enum.py +7 -0
- dodal/devices/{i07 → beamlines/i07}/dcm.py +2 -3
- dodal/devices/{i07 → beamlines/i07}/id.py +8 -9
- dodal/devices/beamlines/i09/__init__.py +3 -0
- dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/hard_energy.py +5 -6
- dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/hard_undulator_functions.py +19 -16
- dodal/devices/{i10 → beamlines/i10}/diagnostics.py +4 -3
- dodal/devices/{i10 → beamlines/i10}/i10_apple2.py +31 -45
- dodal/devices/{i10 → beamlines/i10}/rasor/rasor_current_amp.py +1 -24
- dodal/devices/{i10 → beamlines/i10}/rasor/rasor_motors.py +2 -2
- dodal/devices/{i10 → beamlines/i10}/slits.py +5 -3
- dodal/devices/beamlines/i10_1/__init__.py +9 -0
- dodal/devices/beamlines/i10_1/electromagnet/magnet.py +16 -0
- dodal/devices/beamlines/i10_1/electromagnet/stages.py +14 -0
- dodal/devices/beamlines/i10_1/scaler_cards.py +13 -0
- dodal/devices/{i11 → beamlines/i11}/cyberstar_blower.py +1 -1
- dodal/devices/{i11 → beamlines/i11}/diff_stages.py +4 -6
- dodal/devices/{i11 → beamlines/i11}/mythen.py +3 -4
- dodal/devices/{i11 → beamlines/i11}/nx100robot.py +6 -6
- dodal/devices/{i11 → beamlines/i11}/spinner.py +1 -1
- dodal/devices/{i13_1 → beamlines/i13_1}/merlin.py +1 -1
- dodal/devices/{i15 → beamlines/i15}/dcm.py +1 -2
- dodal/devices/{i15 → beamlines/i15}/focussing_mirror.py +5 -5
- dodal/devices/{i15 → beamlines/i15}/jack.py +2 -2
- dodal/devices/{i15 → beamlines/i15}/multilayer_mirror.py +1 -1
- dodal/devices/{i17 → beamlines/i17}/i17_apple2.py +10 -16
- dodal/devices/{i18 → beamlines/i18}/diode.py +1 -1
- dodal/devices/{i19 → beamlines/i19}/access_controlled/attenuator_motor_squad.py +12 -8
- dodal/devices/{i19 → beamlines/i19}/access_controlled/blueapi_device.py +16 -15
- dodal/devices/beamlines/i19/access_controlled/piezo_control.py +72 -0
- dodal/devices/{i19 → beamlines/i19}/access_controlled/shutter.py +11 -9
- dodal/devices/{i19 → beamlines/i19}/backlight.py +3 -1
- dodal/devices/{i19 → beamlines/i19}/mapt_configuration.py +2 -1
- dodal/devices/{i19 → beamlines/i19}/pin_col_stages.py +11 -8
- dodal/devices/beamlines/i19/pin_tip.py +32 -0
- dodal/devices/beamlines/i21/__init__.py +3 -0
- dodal/devices/{i22 → beamlines/i22}/dcm.py +1 -2
- dodal/devices/{i22 → beamlines/i22}/fswitch.py +1 -3
- dodal/devices/{i22 → beamlines/i22}/nxsas.py +5 -4
- dodal/devices/{i24 → beamlines/i24}/beam_center.py +1 -1
- dodal/devices/{i24 → beamlines/i24}/beamstop.py +2 -2
- dodal/devices/{i24 → beamlines/i24}/commissioning_jungfrau.py +3 -2
- dodal/devices/{i24 → beamlines/i24}/dcm.py +1 -3
- dodal/devices/{i24 → beamlines/i24}/dual_backlight.py +3 -3
- dodal/devices/{i24 → beamlines/i24}/pmac.py +9 -7
- dodal/devices/{p60 → beamlines/p60}/lab_xray_source.py +1 -1
- dodal/devices/beamlines/p99/__init__.py +0 -0
- dodal/devices/{p99 → beamlines/p99}/andor2_point.py +11 -15
- dodal/devices/bimorph_mirror.py +22 -20
- dodal/devices/collimation_table.py +3 -2
- dodal/devices/common_dcm.py +30 -20
- dodal/devices/controllers.py +2 -2
- dodal/devices/cryostream.py +8 -0
- dodal/devices/current_amplifiers/current_amplifier.py +16 -18
- dodal/devices/current_amplifiers/current_amplifier_detector.py +9 -10
- dodal/devices/current_amplifiers/femto.py +8 -9
- dodal/devices/current_amplifiers/sr570.py +16 -16
- dodal/devices/current_amplifiers/struck_scaler_counter.py +5 -5
- dodal/devices/detector/det_resolution.py +9 -8
- dodal/devices/detector/detector.py +4 -2
- dodal/devices/diamond_filter.py +3 -4
- dodal/devices/eiger.py +3 -3
- dodal/devices/eiger_odin.py +1 -1
- dodal/devices/electron_analyser/base/base_controller.py +13 -13
- dodal/devices/electron_analyser/base/base_detector.py +15 -20
- dodal/devices/electron_analyser/base/base_driver_io.py +39 -46
- dodal/devices/electron_analyser/base/base_region.py +27 -30
- dodal/devices/electron_analyser/base/base_util.py +18 -16
- dodal/devices/electron_analyser/base/energy_sources.py +13 -19
- dodal/devices/eurotherm.py +3 -2
- dodal/devices/fast_grid_scan.py +31 -34
- dodal/devices/fast_shutter.py +24 -21
- dodal/devices/flux.py +1 -1
- dodal/devices/focusing_mirror.py +29 -11
- dodal/devices/hutch_shutter.py +6 -6
- dodal/devices/insertion_device/__init__.py +8 -0
- dodal/devices/insertion_device/apple2_controller.py +51 -60
- dodal/devices/insertion_device/apple2_undulator.py +40 -64
- dodal/devices/insertion_device/apple_knot_controller.py +222 -0
- dodal/devices/insertion_device/energy.py +100 -27
- dodal/devices/insertion_device/energy_motor_lookup.py +20 -27
- dodal/devices/insertion_device/lookup_table_models.py +45 -50
- dodal/devices/insertion_device/polarisation.py +1 -1
- dodal/devices/ipin.py +1 -1
- dodal/devices/linkam3.py +7 -5
- dodal/devices/motors.py +107 -19
- dodal/devices/mx_phase1/beamstop.py +2 -4
- dodal/devices/oav/oav_calculations.py +20 -13
- dodal/devices/oav/oav_detector.py +28 -23
- dodal/devices/oav/oav_parameters.py +4 -9
- dodal/devices/oav/oav_to_redis_forwarder.py +22 -18
- dodal/devices/oav/pin_image_recognition/__init__.py +4 -6
- dodal/devices/oav/pin_image_recognition/manual_test.py +1 -2
- dodal/devices/oav/pin_image_recognition/utils.py +30 -32
- dodal/devices/oav/snapshots/grid_overlay.py +10 -9
- dodal/devices/oav/snapshots/snapshot_image_processing.py +15 -13
- dodal/devices/oav/utils.py +9 -12
- dodal/devices/p45.py +3 -9
- dodal/devices/pgm.py +8 -14
- dodal/devices/pressure_jump_cell.py +93 -32
- dodal/devices/qbpm.py +1 -3
- dodal/devices/robot.py +12 -4
- dodal/devices/s4_slit_gaps.py +1 -1
- dodal/devices/selectable_source.py +5 -2
- dodal/devices/slits.py +2 -5
- dodal/devices/smargon.py +2 -3
- dodal/devices/temperture_controller/lakeshore/lakeshore.py +38 -64
- dodal/devices/temperture_controller/lakeshore/lakeshore_io.py +21 -35
- dodal/devices/tetramm.py +7 -7
- dodal/devices/turbo_slit.py +8 -7
- dodal/devices/undulator.py +42 -56
- dodal/devices/util/adjuster_plans.py +2 -3
- dodal/devices/util/epics_util.py +10 -10
- dodal/devices/util/lookup_tables.py +17 -18
- dodal/devices/v2f.py +2 -3
- dodal/devices/watsonmarlow323_pump.py +1 -1
- dodal/devices/xbpm_feedback.py +3 -2
- dodal/devices/xspress3/xspress3.py +8 -11
- dodal/devices/xspress3/xspress3_channel.py +3 -6
- dodal/devices/zebra/zebra.py +6 -7
- dodal/devices/zebra/zebra_constants_mapping.py +11 -7
- dodal/devices/zebra/zebra_controlled_shutter.py +2 -1
- dodal/devices/zocalo/zocalo_interaction.py +14 -14
- dodal/devices/zocalo/zocalo_results.py +33 -33
- dodal/log.py +23 -20
- dodal/plan_stubs/check_topup.py +15 -15
- dodal/plan_stubs/data_session.py +6 -6
- dodal/plan_stubs/motor_utils.py +22 -18
- dodal/plan_stubs/pressure_jump_cell.py +18 -0
- dodal/plan_stubs/wrapped.py +40 -55
- dodal/plans/bimorph.py +63 -52
- dodal/plans/device_setup_plans/__init__.py +5 -0
- dodal/plans/device_setup_plans/setup_pin_tip_params.py +63 -0
- dodal/plans/preprocessors/verify_undulator_gap.py +10 -8
- dodal/plans/spec_path.py +3 -5
- dodal/plans/verify_undulator_gap.py +1 -2
- dodal/plans/wrapped.py +4 -3
- dodal/testing/electron_analyser/device_factory.py +5 -7
- dodal/testing/fixtures/devices/apple2.py +38 -0
- dodal/testing/fixtures/run_engine.py +3 -7
- dodal/testing/fixtures/utils.py +1 -2
- dodal/utils.py +60 -58
- dls_dodal-1.69.0.dist-info/RECORD +0 -338
- dodal/beamline_specific_utils/i05_shared.py +0 -14
- dodal/devices/b07/__init__.py +0 -3
- dodal/devices/i04/beam_centre.py +0 -84
- dodal/devices/i05/__init__.py +0 -3
- dodal/devices/i09/__init__.py +0 -3
- dodal/devices/i21/__init__.py +0 -5
- {dls_dodal-1.69.0.dist-info → dls_dodal-2.0.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.69.0.dist-info → dls_dodal-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.69.0.dist-info → dls_dodal-2.0.0.dist-info}/top_level.txt +0 -0
- /dodal/{beamline_specific_utils → devices/beamlines}/__init__.py +0 -0
- /dodal/devices/{b07 → beamlines/b07}/enums.py +0 -0
- /dodal/devices/{b07_1 → beamlines/b07_1}/enums.py +0 -0
- /dodal/devices/{b16 → beamlines/b16}/__init__.py +0 -0
- /dodal/devices/{i02_1 → beamlines/i02_1}/__init__.py +0 -0
- /dodal/devices/{i02_2 → beamlines/i02_2}/__init__.py +0 -0
- /dodal/devices/{i03 → beamlines/i03}/__init__.py +0 -0
- /dodal/devices/{i03 → beamlines/i03}/constants.py +0 -0
- /dodal/devices/{i04 → beamlines/i04}/__init__.py +0 -0
- /dodal/devices/{i04 → beamlines/i04}/constants.py +0 -0
- /dodal/devices/{i04 → beamlines/i04}/max_pixel.py +0 -0
- /dodal/devices/{i05 → beamlines/i05}/enums.py +0 -0
- /dodal/devices/{i07 → beamlines/i07}/__init__.py +0 -0
- /dodal/devices/{i09 → beamlines/i09}/enums.py +0 -0
- /dodal/devices/{i09_1 → beamlines/i09_1}/__init__.py +0 -0
- /dodal/devices/{i09_1 → beamlines/i09_1}/enums.py +0 -0
- /dodal/devices/{i09_1_shared → beamlines/i09_1_shared}/__init__.py +0 -0
- /dodal/devices/{i09_2_shared → beamlines/i09_2_shared}/__init__.py +0 -0
- /dodal/devices/{i09_2_shared → beamlines/i09_2_shared}/i09_apple2.py +0 -0
- /dodal/devices/{i10 → beamlines/i10}/__init__.py +0 -0
- /dodal/devices/{i10 → beamlines/i10}/i10_setting_data.py +0 -0
- /dodal/devices/{i10 → beamlines/i10}/mirrors.py +0 -0
- /dodal/devices/{i10 → beamlines/i10}/rasor/__init__.py +0 -0
- /dodal/devices/{i10 → beamlines/i10}/rasor/rasor_scaler_cards.py +0 -0
- /dodal/devices/{i11 → beamlines/i10_1/electromagnet}/__init__.py +0 -0
- /dodal/devices/{i13_1 → beamlines/i11}/__init__.py +0 -0
- /dodal/devices/{i15 → beamlines/i13_1}/__init__.py +0 -0
- /dodal/devices/{i13_1 → beamlines/i13_1}/merlin_controller.py +0 -0
- /dodal/devices/{i17 → beamlines/i15}/__init__.py +0 -0
- /dodal/devices/{i15 → beamlines/i15}/laue.py +0 -0
- /dodal/devices/{i15 → beamlines/i15}/motors.py +0 -0
- /dodal/devices/{i15 → beamlines/i15}/rail.py +0 -0
- /dodal/devices/{i18 → beamlines/i17}/__init__.py +0 -0
- /dodal/devices/{i19 → beamlines/i18}/__init__.py +0 -0
- /dodal/devices/{i18 → beamlines/i18}/kb_mirror.py +0 -0
- /dodal/devices/{i19/access_controlled → beamlines/i19}/__init__.py +0 -0
- /dodal/devices/{i20_1 → beamlines/i19/access_controlled}/__init__.py +0 -0
- /dodal/devices/{i19 → beamlines/i19}/access_controlled/hutch_access.py +0 -0
- /dodal/devices/{i19 → beamlines/i19}/beamstop.py +0 -0
- /dodal/devices/{i19 → beamlines/i19}/diffractometer.py +0 -0
- /dodal/devices/{i22 → beamlines/i20_1}/__init__.py +0 -0
- /dodal/devices/{i21 → beamlines/i21}/enums.py +0 -0
- /dodal/devices/{i24 → beamlines/i22}/__init__.py +0 -0
- /dodal/devices/{p99 → beamlines/i24}/__init__.py +0 -0
- /dodal/devices/{i24 → beamlines/i24}/aperture.py +0 -0
- /dodal/devices/{i24 → beamlines/i24}/focus_mirrors.py +0 -0
- /dodal/devices/{i24 → beamlines/i24}/vgonio.py +0 -0
- /dodal/devices/{p60 → beamlines/p60}/__init__.py +0 -0
- /dodal/devices/{p60 → beamlines/p60}/enums.py +0 -0
- /dodal/devices/{p99 → beamlines/p99}/sample_stage.py +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from math import isclose
|
|
2
|
+
from typing import Generic
|
|
3
|
+
|
|
4
|
+
from numpy import sign
|
|
5
|
+
|
|
6
|
+
from dodal.common import Rectangle2D
|
|
7
|
+
from dodal.devices.insertion_device import (
|
|
8
|
+
Apple2,
|
|
9
|
+
Apple2Controller,
|
|
10
|
+
Apple2Val,
|
|
11
|
+
EnergyMotorConvertor,
|
|
12
|
+
)
|
|
13
|
+
from dodal.devices.insertion_device.apple2_undulator import (
|
|
14
|
+
Apple2LockedPhasesVal,
|
|
15
|
+
PhaseAxesType,
|
|
16
|
+
)
|
|
17
|
+
from dodal.devices.insertion_device.enum import Pol
|
|
18
|
+
from dodal.log import LOGGER
|
|
19
|
+
|
|
20
|
+
APPLE_KNOT_MAXIMUM_GAP_MOTOR_POSITION = 100.0
|
|
21
|
+
APPLE_KNOT_MAXIMUM_PHASE_MOTOR_POSITION = 70.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AppleKnotPathFinder:
|
|
25
|
+
"""Class to find a safe path for AppleKnot undulator moves that avoids the exclusion
|
|
26
|
+
zone around 0-0 gap-phase. We rely on axis-aligned (manhattan) moves and splitting
|
|
27
|
+
moves that cross zero phase into two segments via an intermediate point at zero
|
|
28
|
+
phase and a safe gap value. We ASSUME the exclusion zones are rectangles aligned
|
|
29
|
+
with the axes in a shape of hanoi tower centered at (0,0).
|
|
30
|
+
Gap and phase motors are NOT moved together but instead are moved sequentially.
|
|
31
|
+
Sequential move guarantees safe pass avoiding exslusion zones.
|
|
32
|
+
We can not use asynchronous move of gap and phase because we can not currently rely
|
|
33
|
+
on gap and phase motors relative speed.
|
|
34
|
+
See https://confluence.diamond.ac.uk/x/vQENAg for more details.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
exclusion_zone: tuple[Rectangle2D, ...],
|
|
40
|
+
) -> None:
|
|
41
|
+
# Define the exclusion zone rectangles around (0,0)
|
|
42
|
+
self.exclusion_zone = exclusion_zone
|
|
43
|
+
|
|
44
|
+
def get_apple_knot_val_path(
|
|
45
|
+
self, start_val: Apple2Val, end_val: Apple2Val
|
|
46
|
+
) -> tuple[Apple2Val, ...]:
|
|
47
|
+
"""Get a list of Apple2Val representing the path from start to end avoiding
|
|
48
|
+
exclusion zones.
|
|
49
|
+
"""
|
|
50
|
+
apple_knot_val_path = ()
|
|
51
|
+
# Defensive checks for no movement
|
|
52
|
+
if (
|
|
53
|
+
start_val.gap == end_val.gap
|
|
54
|
+
and start_val.phase.top_outer == end_val.phase.top_outer
|
|
55
|
+
):
|
|
56
|
+
LOGGER.warning("Start point same as end point, no path calculated.")
|
|
57
|
+
return apple_knot_val_path
|
|
58
|
+
for zone in self.exclusion_zone:
|
|
59
|
+
for value in (start_val, end_val):
|
|
60
|
+
if zone.contains(value.phase.top_outer, value.gap):
|
|
61
|
+
LOGGER.warning(
|
|
62
|
+
"Start point is inside exclusion zone, no path calculated."
|
|
63
|
+
)
|
|
64
|
+
return apple_knot_val_path
|
|
65
|
+
apple_knot_val_path += (start_val,)
|
|
66
|
+
# Split the move if start and end are on opposite sides of zero phase
|
|
67
|
+
# TBD This can be potentially improved to always have max 3 sections in path!
|
|
68
|
+
# Currently in copies java class logic
|
|
69
|
+
if (
|
|
70
|
+
sign(start_val.phase.top_outer) == (-1) * sign(end_val.phase.top_outer)
|
|
71
|
+
and sign(start_val.phase.top_outer) != 0
|
|
72
|
+
):
|
|
73
|
+
apple_knot_val_path += (
|
|
74
|
+
self._get_zero_phase_crossing_point(start_val, end_val),
|
|
75
|
+
)
|
|
76
|
+
apple_knot_val_path += (end_val,)
|
|
77
|
+
return self._apple_knot_manhattan_path(apple_knot_val_path)
|
|
78
|
+
|
|
79
|
+
def _apple_knot_manhattan_path(
|
|
80
|
+
self, apple_knot_val_path: tuple[Apple2Val, ...]
|
|
81
|
+
) -> tuple[Apple2Val, ...]:
|
|
82
|
+
"""Convert a list of Apple2Val into a manhattan path avoiding exclusion zones.
|
|
83
|
+
Here all moves are done in axis-aligned steps (gap first then phase or vice
|
|
84
|
+
versa).
|
|
85
|
+
List of points is expanded to include intermediate points as needed so each move
|
|
86
|
+
happens within one sign of gap and phase (including zero phase).
|
|
87
|
+
For convenience we define:phase increase as West-East axis and gap increase
|
|
88
|
+
as South-North axis. Only SW move in negative phase region and SE move in
|
|
89
|
+
positive phase region need a PHASE first then GAP move, the rest needs GAP
|
|
90
|
+
first then PHASE move or there is no difference in order.
|
|
91
|
+
"""
|
|
92
|
+
final_path = []
|
|
93
|
+
for i in range(len(apple_knot_val_path) - 1):
|
|
94
|
+
start_val = apple_knot_val_path[i]
|
|
95
|
+
end_val = apple_knot_val_path[i + 1]
|
|
96
|
+
final_path.append(start_val)
|
|
97
|
+
# Direct move along one axis, no intermediate point needed
|
|
98
|
+
if (
|
|
99
|
+
end_val.phase.top_outer == start_val.phase.top_outer
|
|
100
|
+
or end_val.gap == start_val.gap
|
|
101
|
+
):
|
|
102
|
+
continue
|
|
103
|
+
# Determine move order based on quadrant rules
|
|
104
|
+
if end_val.gap <= start_val.gap and abs(end_val.phase.top_outer) > abs(
|
|
105
|
+
start_val.phase.top_outer
|
|
106
|
+
):
|
|
107
|
+
# Move PHASE first then GAP (SW move in negative phase or SE move in positive phase)
|
|
108
|
+
intermediate_val = Apple2Val(gap=start_val.gap, phase=end_val.phase)
|
|
109
|
+
final_path.append(intermediate_val)
|
|
110
|
+
else:
|
|
111
|
+
# Move GAP first then PHASE (other moves)
|
|
112
|
+
intermediate_val = Apple2Val(gap=end_val.gap, phase=start_val.phase)
|
|
113
|
+
final_path.append(intermediate_val)
|
|
114
|
+
final_path.append(apple_knot_val_path[-1])
|
|
115
|
+
return tuple(final_path)
|
|
116
|
+
|
|
117
|
+
def _get_zero_phase_crossing_point(
|
|
118
|
+
self, start_val: Apple2Val, end_val: Apple2Val
|
|
119
|
+
) -> Apple2Val:
|
|
120
|
+
# Calculate the point where phase crosses zero
|
|
121
|
+
max_exclusion_gap = (
|
|
122
|
+
max([zone.get_max_y() for zone in self.exclusion_zone])
|
|
123
|
+
if self.exclusion_zone
|
|
124
|
+
else 0.0
|
|
125
|
+
)
|
|
126
|
+
return Apple2Val(
|
|
127
|
+
gap=max(
|
|
128
|
+
(start_val.gap + end_val.gap) / 2, max_exclusion_gap
|
|
129
|
+
), # Ensure gap is above a minimum value
|
|
130
|
+
phase=Apple2LockedPhasesVal(
|
|
131
|
+
top_outer=0.0,
|
|
132
|
+
btm_inner=0.0,
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class AppleKnotController(
|
|
138
|
+
Apple2Controller[Apple2[PhaseAxesType]], Generic[PhaseAxesType]
|
|
139
|
+
):
|
|
140
|
+
"""Controller for Apple Knot undulator with unique feature of calculating a move
|
|
141
|
+
path through gap and phase space avoiding the exclusion zone around 0-0 gap-phase.
|
|
142
|
+
See https://confluence.diamond.ac.uk/x/vQENAg for more details.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
apple: Apple2[PhaseAxesType],
|
|
148
|
+
gap_energy_motor_converter: EnergyMotorConvertor,
|
|
149
|
+
phase_energy_motor_converter: EnergyMotorConvertor,
|
|
150
|
+
path_finder: AppleKnotPathFinder,
|
|
151
|
+
maximum_gap_motor_position: float = APPLE_KNOT_MAXIMUM_GAP_MOTOR_POSITION,
|
|
152
|
+
maximum_phase_motor_position: float = APPLE_KNOT_MAXIMUM_PHASE_MOTOR_POSITION,
|
|
153
|
+
units: str = "eV",
|
|
154
|
+
name: str = "",
|
|
155
|
+
) -> None:
|
|
156
|
+
self.path_finder = path_finder
|
|
157
|
+
super().__init__(
|
|
158
|
+
apple2=apple,
|
|
159
|
+
gap_energy_motor_converter=gap_energy_motor_converter,
|
|
160
|
+
phase_energy_motor_converter=phase_energy_motor_converter,
|
|
161
|
+
maximum_gap_motor_position=maximum_gap_motor_position,
|
|
162
|
+
maximum_phase_motor_position=maximum_phase_motor_position,
|
|
163
|
+
units=units,
|
|
164
|
+
name=name,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
async def _set_energy(self, energy: float) -> None:
|
|
168
|
+
await self.check_top_bottom_phase_match()
|
|
169
|
+
pol = await self._check_and_get_pol_setpoint()
|
|
170
|
+
await self._combined_move(energy, pol)
|
|
171
|
+
self._energy_set(energy)
|
|
172
|
+
|
|
173
|
+
async def check_top_bottom_phase_match(self) -> None:
|
|
174
|
+
"""Check that the top and bottom phase motors are in sync.
|
|
175
|
+
Raise an error if they are not within tolerance.
|
|
176
|
+
"""
|
|
177
|
+
current_phase_top = float(
|
|
178
|
+
await self.apple2().phase().top_outer.user_readback.get_value()
|
|
179
|
+
)
|
|
180
|
+
current_phase_bottom = float(
|
|
181
|
+
await self.apple2().phase().btm_inner.user_readback.get_value()
|
|
182
|
+
)
|
|
183
|
+
if not isclose(current_phase_top, current_phase_bottom, abs_tol=5e-2):
|
|
184
|
+
raise RuntimeError(
|
|
185
|
+
f"Upper phase {current_phase_top} and lower phase {current_phase_bottom} values are not close enough."
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
async def _combined_move(self, energy: float, pol: Pol) -> None:
|
|
189
|
+
# get current apple2 value
|
|
190
|
+
current_phase_top = float(
|
|
191
|
+
await self.apple2().phase().top_outer.user_readback.get_value()
|
|
192
|
+
)
|
|
193
|
+
current_gap = float(await self.apple2().gap().user_readback.get_value())
|
|
194
|
+
current_apple2_val = self._get_apple2_value(
|
|
195
|
+
current_gap, current_phase_top, Pol.NONE
|
|
196
|
+
)
|
|
197
|
+
# get target apple2 value
|
|
198
|
+
target_gap = self.gap_energy_motor_converter(energy=energy, pol=pol)
|
|
199
|
+
target_phase = self.phase_energy_motor_converter(energy=energy, pol=pol)
|
|
200
|
+
target_apple2_val = self._get_apple2_value(target_gap, target_phase, pol)
|
|
201
|
+
# get path avoiding exclusion zone
|
|
202
|
+
manhattan_path = self.path_finder.get_apple_knot_val_path(
|
|
203
|
+
current_apple2_val, target_apple2_val
|
|
204
|
+
)
|
|
205
|
+
if manhattan_path == ():
|
|
206
|
+
raise RuntimeError("No valid path found for move avoiding exclusion zones.")
|
|
207
|
+
# execute the moves along the path
|
|
208
|
+
for apple2_val in manhattan_path:
|
|
209
|
+
LOGGER.info(f"Moving to apple2 values: {apple2_val}")
|
|
210
|
+
await self.apple2().set(id_motor_values=apple2_val)
|
|
211
|
+
|
|
212
|
+
def _get_apple2_value(self, gap: float, phase: float, pol: Pol) -> Apple2Val:
|
|
213
|
+
apple2_val = Apple2Val(
|
|
214
|
+
gap=gap,
|
|
215
|
+
phase=Apple2LockedPhasesVal(
|
|
216
|
+
top_outer=phase,
|
|
217
|
+
btm_inner=phase,
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
LOGGER.info(f"Getting apple2 value for gap={gap}, phase={phase}.")
|
|
221
|
+
LOGGER.info(f"Apple2 motor values: {apple2_val}.")
|
|
222
|
+
return apple2_val
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import asyncio
|
|
3
3
|
|
|
4
|
-
from bluesky.protocols import Movable
|
|
4
|
+
from bluesky.protocols import Flyable, Movable, Preparable
|
|
5
5
|
from ophyd_async.core import (
|
|
6
6
|
AsyncStatus,
|
|
7
|
+
FlyMotorInfo,
|
|
7
8
|
Reference,
|
|
8
9
|
SignalRW,
|
|
9
10
|
StandardReadable,
|
|
10
11
|
StandardReadableFormat,
|
|
12
|
+
WatchableAsyncStatus,
|
|
13
|
+
error_if_none,
|
|
11
14
|
soft_signal_rw,
|
|
12
15
|
)
|
|
13
16
|
from ophyd_async.epics.motor import Motor
|
|
14
17
|
|
|
15
|
-
from dodal.devices.insertion_device import
|
|
18
|
+
from dodal.devices.insertion_device import (
|
|
19
|
+
MAXIMUM_MOVE_TIME,
|
|
20
|
+
Apple2Controller,
|
|
21
|
+
Apple2Type,
|
|
22
|
+
)
|
|
16
23
|
from dodal.log import LOGGER
|
|
17
24
|
|
|
18
25
|
|
|
@@ -28,26 +35,76 @@ class InsertionDeviceEnergyBase(abc.ABC, StandardReadable, Movable):
|
|
|
28
35
|
async def set(self, energy: float) -> None: ...
|
|
29
36
|
|
|
30
37
|
|
|
31
|
-
class
|
|
32
|
-
"""
|
|
33
|
-
Compound device to set both ID and energy motor at the same time with an option to add an offset.
|
|
34
|
-
"""
|
|
38
|
+
class InsertionDeviceEnergy(InsertionDeviceEnergyBase, Preparable, Flyable):
|
|
39
|
+
"""Apple2 ID energy movable device."""
|
|
35
40
|
|
|
36
41
|
def __init__(
|
|
37
|
-
self,
|
|
42
|
+
self, id_controller: Apple2Controller[Apple2Type], name: str = ""
|
|
38
43
|
) -> None:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
----------
|
|
42
|
-
|
|
43
|
-
id_energy: InsertionDeviceEnergy
|
|
44
|
-
An InsertionDeviceEnergy device.
|
|
45
|
-
mono: Motor
|
|
46
|
-
A Motor(energy) device.
|
|
47
|
-
name:
|
|
48
|
-
New device name.
|
|
49
|
-
"""
|
|
44
|
+
self.energy = Reference(id_controller.energy)
|
|
45
|
+
self._id_controller = Reference(id_controller)
|
|
50
46
|
super().__init__(name=name)
|
|
47
|
+
|
|
48
|
+
self.add_readables([self.energy()], StandardReadableFormat.HINTED_SIGNAL)
|
|
49
|
+
|
|
50
|
+
@AsyncStatus.wrap
|
|
51
|
+
async def set(self, energy: float) -> None:
|
|
52
|
+
LOGGER.info(f"Setting insertion device energy to {energy}.")
|
|
53
|
+
await self.energy().set(energy, timeout=MAXIMUM_MOVE_TIME)
|
|
54
|
+
|
|
55
|
+
@AsyncStatus.wrap
|
|
56
|
+
async def prepare(self, value: FlyMotorInfo) -> None:
|
|
57
|
+
"""Convert FlyMotorInfo from energy to gap motion and move phase motor to mid point."""
|
|
58
|
+
mid_energy = (value.start_position + value.end_position) / 2.0
|
|
59
|
+
LOGGER.info(
|
|
60
|
+
f"Preparing for fly energy scan, move {self._id_controller().apple2().phase} to {mid_energy}"
|
|
61
|
+
)
|
|
62
|
+
await self.set(energy=mid_energy)
|
|
63
|
+
current_pol = await self._id_controller().polarisation_setpoint.get_value()
|
|
64
|
+
start_position = self._id_controller().gap_energy_motor_converter(
|
|
65
|
+
energy=value.start_position,
|
|
66
|
+
pol=current_pol,
|
|
67
|
+
)
|
|
68
|
+
end_position = self._id_controller().gap_energy_motor_converter(
|
|
69
|
+
energy=value.end_position, pol=current_pol
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
gap_fly_motor_info = FlyMotorInfo(
|
|
73
|
+
start_position=start_position,
|
|
74
|
+
end_position=end_position,
|
|
75
|
+
time_for_move=value.time_for_move,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
LOGGER.info(
|
|
79
|
+
f"Flyscan info in energy: {value}. "
|
|
80
|
+
+ f"Flyscan info in gap: {gap_fly_motor_info}. "
|
|
81
|
+
+ f"Speed: {gap_fly_motor_info.velocity}."
|
|
82
|
+
)
|
|
83
|
+
await self._id_controller().apple2().gap().prepare(value=gap_fly_motor_info)
|
|
84
|
+
|
|
85
|
+
@AsyncStatus.wrap
|
|
86
|
+
async def kickoff(self):
|
|
87
|
+
await self._id_controller().apple2().gap().kickoff()
|
|
88
|
+
|
|
89
|
+
def complete(self) -> WatchableAsyncStatus:
|
|
90
|
+
return self._id_controller().apple2().gap().complete()
|
|
91
|
+
|
|
92
|
+
async def get_id_acceleration_time(self) -> float:
|
|
93
|
+
return await self._id_controller().apple2().gap().acceleration_time.get_value()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class BeamEnergy(StandardReadable, Movable[float], Preparable, Flyable):
|
|
97
|
+
"""Compound device to set both ID and energy motor at the same time with an option to add an offset.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
id_energy (InsertionDeviceEnergy): An InsertionDeviceEnergy device.
|
|
101
|
+
mono (Motor): A Motor(energy) device.
|
|
102
|
+
name: New device name.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self, id_energy: InsertionDeviceEnergy, mono: Motor, name: str = ""
|
|
107
|
+
) -> None:
|
|
51
108
|
self._id_energy = Reference(id_energy)
|
|
52
109
|
self._mono_energy = Reference(mono)
|
|
53
110
|
|
|
@@ -61,6 +118,7 @@ class BeamEnergy(StandardReadable, Movable[float]):
|
|
|
61
118
|
|
|
62
119
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
63
120
|
self.id_energy_offset = soft_signal_rw(float, initial_value=0)
|
|
121
|
+
super().__init__(name=name)
|
|
64
122
|
|
|
65
123
|
@AsyncStatus.wrap
|
|
66
124
|
async def set(self, energy: float) -> None:
|
|
@@ -72,17 +130,32 @@ class BeamEnergy(StandardReadable, Movable[float]):
|
|
|
72
130
|
self._mono_energy().set(energy),
|
|
73
131
|
)
|
|
74
132
|
|
|
133
|
+
@AsyncStatus.wrap
|
|
134
|
+
async def prepare(self, value: FlyMotorInfo) -> None:
|
|
135
|
+
await asyncio.gather(
|
|
136
|
+
self._id_energy().prepare(value), self._mono_energy().prepare(value)
|
|
137
|
+
)
|
|
75
138
|
|
|
76
|
-
|
|
77
|
-
|
|
139
|
+
@AsyncStatus.wrap
|
|
140
|
+
async def kickoff(self):
|
|
141
|
+
pgm_acceleration_time, gap_acceleration_time = await asyncio.gather(
|
|
142
|
+
self._mono_energy().acceleration_time.get_value(),
|
|
143
|
+
self._id_energy().get_id_acceleration_time(),
|
|
144
|
+
)
|
|
145
|
+
start_offset_time = pgm_acceleration_time - gap_acceleration_time
|
|
78
146
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
147
|
+
await self._mono_energy().kickoff()
|
|
148
|
+
await asyncio.sleep(start_offset_time)
|
|
149
|
+
await self._id_energy().kickoff()
|
|
150
|
+
self._fly_status = self._combined_fly_status()
|
|
82
151
|
|
|
83
|
-
|
|
152
|
+
def complete(self) -> AsyncStatus:
|
|
153
|
+
"""Stop when both pgm and id is done moving."""
|
|
154
|
+
fly_status = error_if_none(self._fly_status, "kickoff not called")
|
|
155
|
+
return fly_status
|
|
84
156
|
|
|
85
157
|
@AsyncStatus.wrap
|
|
86
|
-
async def
|
|
87
|
-
|
|
88
|
-
|
|
158
|
+
async def _combined_fly_status(self):
|
|
159
|
+
status_pgm = self._mono_energy().complete()
|
|
160
|
+
status_id = self._id_energy().complete()
|
|
161
|
+
await asyncio.gather(status_pgm, status_id)
|
|
@@ -11,12 +11,12 @@ from dodal.devices.insertion_device.lookup_table_models import (
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class EnergyMotorLookup:
|
|
14
|
-
"""
|
|
15
|
-
Handles a lookup table for Apple2 ID, converting energy/polarisation to a motor
|
|
14
|
+
"""Handles a lookup table for Apple2 ID, converting energy/polarisation to a motor
|
|
16
15
|
position.
|
|
17
16
|
|
|
18
|
-
After update_lookup_table() has populated the lookup table,
|
|
19
|
-
can be used to compute gap / phase for a requested
|
|
17
|
+
After update_lookup_table() has populated the lookup table,
|
|
18
|
+
`find_value_in_lookup_table()` can be used to compute gap / phase for a requested
|
|
19
|
+
energy / polarisation pair.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
def __init__(self, lut: LookupTable | None = None):
|
|
@@ -26,24 +26,19 @@ class EnergyMotorLookup:
|
|
|
26
26
|
|
|
27
27
|
def update_lookup_table(self) -> None:
|
|
28
28
|
"""Do nothing by default. Sub classes may override this method to provide logic
|
|
29
|
-
on what updating lookup table does.
|
|
29
|
+
on what updating lookup table does.
|
|
30
|
+
"""
|
|
30
31
|
pass
|
|
31
32
|
|
|
32
33
|
def find_value_in_lookup_table(self, energy: float, pol: Pol) -> float:
|
|
33
|
-
"""
|
|
34
|
-
Convert energy and polarisation to a value from the lookup table.
|
|
34
|
+
"""Convert energy and polarisation to a value from the lookup table.
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Desired energy.
|
|
40
|
-
pol : Pol
|
|
41
|
-
Polarisation mode.
|
|
36
|
+
Args:
|
|
37
|
+
energy (float): Desired energy.
|
|
38
|
+
pol (Pol): Polarisation mode.
|
|
42
39
|
|
|
43
40
|
Returns:
|
|
44
|
-
|
|
45
|
-
float
|
|
46
|
-
gap / phase motor position from the lookup table.
|
|
41
|
+
float: gap / phase motor position from the lookup table.
|
|
47
42
|
"""
|
|
48
43
|
# if lut is empty, force an update to pull updated lut incase subclasses have
|
|
49
44
|
# implemented it.
|
|
@@ -55,7 +50,15 @@ class EnergyMotorLookup:
|
|
|
55
50
|
|
|
56
51
|
class ConfigServerEnergyMotorLookup(EnergyMotorLookup):
|
|
57
52
|
"""Fetches and parses lookup table (csv) from a config server, supports dynamic
|
|
58
|
-
updates, and validates input.
|
|
53
|
+
updates, and validates input.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
config_client (ConfigServer): The config server client to fetch the look up
|
|
57
|
+
table data.
|
|
58
|
+
lut_config (LookupTableColumnConfig): Configuration that defines how to
|
|
59
|
+
process file contents into a LookupTable.
|
|
60
|
+
path (Path): File path to the lookup table.
|
|
61
|
+
"""
|
|
59
62
|
|
|
60
63
|
def __init__(
|
|
61
64
|
self,
|
|
@@ -63,16 +66,6 @@ class ConfigServerEnergyMotorLookup(EnergyMotorLookup):
|
|
|
63
66
|
lut_config: LookupTableColumnConfig,
|
|
64
67
|
path: Path,
|
|
65
68
|
):
|
|
66
|
-
"""
|
|
67
|
-
Parameters:
|
|
68
|
-
-----------
|
|
69
|
-
config_client:
|
|
70
|
-
The config server client to fetch the look up table data.
|
|
71
|
-
lut_config:
|
|
72
|
-
Configuration that defines how to process file contents into a LookupTable
|
|
73
|
-
path:
|
|
74
|
-
File path to the lookup table.
|
|
75
|
-
"""
|
|
76
69
|
self.path = path
|
|
77
70
|
self.config_client = config_client
|
|
78
71
|
self.lut_config = lut_config
|
|
@@ -6,21 +6,21 @@ in-memory dictionary format used by the Apple2 controllers.
|
|
|
6
6
|
|
|
7
7
|
Data format produced
|
|
8
8
|
The lookup-table dictionary created by convert_csv_to_lookup() follows this
|
|
9
|
-
structure
|
|
10
|
-
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
9
|
+
structure::
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
"POL_MODE": {
|
|
13
|
+
"energy_entries": [
|
|
14
|
+
{
|
|
15
|
+
"low": <float>,
|
|
16
|
+
"high": <float>,
|
|
17
|
+
"poly": <numpy.poly1d>
|
|
18
|
+
},
|
|
19
|
+
...
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
...
|
|
23
|
+
}
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
import csv
|
|
@@ -76,7 +76,9 @@ class Source(NamedTuple):
|
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
class LookupTableColumnConfig(BaseModel):
|
|
79
|
-
"""Configuration on how to process a csv file columns into a LookupTable data
|
|
79
|
+
"""Configuration on how to process a csv file columns into a LookupTable data
|
|
80
|
+
model.
|
|
81
|
+
"""
|
|
80
82
|
|
|
81
83
|
source: A[
|
|
82
84
|
Source | None,
|
|
@@ -113,7 +115,9 @@ class EnergyCoverageEntry(BaseModel):
|
|
|
113
115
|
def validate_and_convert_poly(
|
|
114
116
|
cls: type[Self], value: np.poly1d | list
|
|
115
117
|
) -> np.poly1d:
|
|
116
|
-
"""If reading from serialized data, it will be using a list. Convert to
|
|
118
|
+
"""If reading from serialized data, it will be using a list. Convert to
|
|
119
|
+
np.poly1d.
|
|
120
|
+
"""
|
|
117
121
|
if isinstance(value, list):
|
|
118
122
|
return np.poly1d(value)
|
|
119
123
|
return value
|
|
@@ -164,15 +168,12 @@ class EnergyCoverage(BaseModel):
|
|
|
164
168
|
return self.energy_entries[-1].max_energy
|
|
165
169
|
|
|
166
170
|
def get_poly(self, energy: float) -> np.poly1d:
|
|
167
|
-
"""
|
|
168
|
-
Return the numpy.poly1d polynomial applicable for the given energy.
|
|
171
|
+
"""Return the numpy.poly1d polynomial applicable for the given energy.
|
|
169
172
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Energy value in the same units used to create the lookup table.
|
|
173
|
+
Args:
|
|
174
|
+
energy (float): Energy value in the same units used to create the lookup
|
|
175
|
+
table.
|
|
174
176
|
"""
|
|
175
|
-
|
|
176
177
|
if not self.min_energy <= energy <= self.max_energy:
|
|
177
178
|
raise ValueError(
|
|
178
179
|
f"Demanding energy must lie between {self.min_energy} and {self.max_energy}!"
|
|
@@ -188,7 +189,8 @@ class EnergyCoverage(BaseModel):
|
|
|
188
189
|
|
|
189
190
|
def get_energy_index(self, energy: float) -> int | None:
|
|
190
191
|
"""Binary search assumes self.energy_entries is sorted by min_energy.
|
|
191
|
-
Return index or None if not found.
|
|
192
|
+
Return index or None if not found.
|
|
193
|
+
"""
|
|
192
194
|
max_index = len(self.energy_entries) - 1
|
|
193
195
|
min_index = 0
|
|
194
196
|
while min_index <= max_index:
|
|
@@ -204,9 +206,8 @@ class EnergyCoverage(BaseModel):
|
|
|
204
206
|
|
|
205
207
|
|
|
206
208
|
class LookupTable(RootModel[dict[Pol, EnergyCoverage]]):
|
|
207
|
-
"""
|
|
208
|
-
|
|
209
|
-
values to Apple2 motor positions.
|
|
209
|
+
"""Specialised lookup table for insertion devices to relate the energy and
|
|
210
|
+
polarisation values to Apple2 motor positions.
|
|
210
211
|
"""
|
|
211
212
|
|
|
212
213
|
model_config = ConfigDict(frozen=True)
|
|
@@ -222,7 +223,8 @@ class LookupTable(RootModel[dict[Pol, EnergyCoverage]]):
|
|
|
222
223
|
energy_coverage: list[EnergyCoverage],
|
|
223
224
|
) -> Self:
|
|
224
225
|
"""Generate a LookupTable containing multiple EnergyCoverage
|
|
225
|
-
for provided polarisations.
|
|
226
|
+
for provided polarisations.
|
|
227
|
+
"""
|
|
226
228
|
root_data = dict(zip(pols, energy_coverage, strict=False))
|
|
227
229
|
return cls(root=root_data)
|
|
228
230
|
|
|
@@ -231,15 +233,12 @@ class LookupTable(RootModel[dict[Pol, EnergyCoverage]]):
|
|
|
231
233
|
energy: float,
|
|
232
234
|
pol: Pol,
|
|
233
235
|
) -> np.poly1d:
|
|
234
|
-
"""
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
Energy value in the same units used to create the lookup table.
|
|
241
|
-
pol:
|
|
242
|
-
Polarisation mode (Pol enum).
|
|
236
|
+
"""Return the numpy.poly1d polynomial applicable for the given energy and
|
|
237
|
+
polarisation.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
energy (float): Energy value in the same units used to create the lookup table.
|
|
241
|
+
pol (Pol): Polarisation mode enum.
|
|
243
242
|
"""
|
|
244
243
|
return self.root[pol].get_poly(energy)
|
|
245
244
|
|
|
@@ -249,21 +248,17 @@ def convert_csv_to_lookup(
|
|
|
249
248
|
lut_config: LookupTableColumnConfig,
|
|
250
249
|
skip_line_start_with: str = "#",
|
|
251
250
|
) -> LookupTable:
|
|
252
|
-
"""
|
|
253
|
-
Convert CSV content into the Apple2 lookup-table dictionary.
|
|
251
|
+
"""Convert CSV content into the Apple2 lookup-table dictionary.
|
|
254
252
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
skip_line_start_with
|
|
262
|
-
Lines beginning with this prefix are skipped (default "#").
|
|
253
|
+
Args:
|
|
254
|
+
file_contents (str): The CSV file contents as string.
|
|
255
|
+
lut_config (LookupTableColumnConfig): The configuration that how to process the
|
|
256
|
+
file_contents into a LookupTable.
|
|
257
|
+
skip_line_start_with (str, optional): Lines beginning with this prefix are
|
|
258
|
+
skipped (default "#").
|
|
263
259
|
|
|
264
260
|
Returns:
|
|
265
|
-
|
|
266
|
-
LookupTable
|
|
261
|
+
LookupTable
|
|
267
262
|
"""
|
|
268
263
|
temp_mode_entries: dict[Pol, list[EnergyCoverageEntry]] = {}
|
|
269
264
|
|
|
@@ -29,7 +29,7 @@ class InsertionDevicePolarisation(StandardReadable, Locatable[Pol]):
|
|
|
29
29
|
await self.polarisation().set(pol, timeout=MAXIMUM_MOVE_TIME)
|
|
30
30
|
|
|
31
31
|
async def locate(self) -> Location[Pol]:
|
|
32
|
-
"""Return the current polarisation"""
|
|
32
|
+
"""Return the current polarisation."""
|
|
33
33
|
setpoint, readback = await asyncio.gather(
|
|
34
34
|
self.polarisation_setpoint().get_value(), self.polarisation().get_value()
|
|
35
35
|
)
|
dodal/devices/ipin.py
CHANGED
|
@@ -20,7 +20,7 @@ class IPinGain(SubsetEnum):
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class IPin(StandardReadable):
|
|
23
|
-
"""Simple device to get the ipin reading"""
|
|
23
|
+
"""Simple device to get the ipin reading."""
|
|
24
24
|
|
|
25
25
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
26
26
|
with self.add_children_as_readables(
|
dodal/devices/linkam3.py
CHANGED
|
@@ -14,15 +14,17 @@ class PumpControl(StrictEnum):
|
|
|
14
14
|
# TODO: Make use of Status PV:
|
|
15
15
|
# https://github.com/DiamondLightSource/dodal/issues/338
|
|
16
16
|
class Linkam3(StandardReadable):
|
|
17
|
-
"""Device to represent a Linkam3 temperature controller
|
|
17
|
+
"""Device to represent a Linkam3 temperature controller.
|
|
18
18
|
|
|
19
19
|
Attributes:
|
|
20
|
-
tolerance (float): Deadband around the setpoint within which the position is
|
|
21
|
-
|
|
20
|
+
tolerance (float): Deadband around the setpoint within which the position is
|
|
21
|
+
assumed to have been reached.
|
|
22
|
+
settle_time (int): The delay between reaching the setpoint and the move being
|
|
23
|
+
considered complete.
|
|
22
24
|
|
|
23
25
|
Args:
|
|
24
|
-
prefix (str): PV prefix for this device
|
|
25
|
-
name (str):
|
|
26
|
+
prefix (str): PV prefix for this device.
|
|
27
|
+
name (str): Unique name for this device.
|
|
26
28
|
"""
|
|
27
29
|
|
|
28
30
|
tolerance: float = 0.5
|