feat(aerotech): minimal_run_up option, run-up only the ramp distance
CI for csaxs_bec / test (push) Successful in 3m39s
CI for csaxs_bec / test (push) Successful in 3m39s
aerotech_cont_grid(..., minimal_run_up=True): run-up / run-out = v^2 / (2a), without the 2x margin and settle_time, for analysis with unevenly spaced (captured) positions; at least 0.01 so the fast axis never stops and reverses at the edge of the next line's PSO window (0.25 um at 0.5 mm/s otherwise). Shown in the statistics header and the limit message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
This commit is contained in:
1 parent
c86e825e61
commit
3c551ba38e
2 files changed
+62
-5
No files matched your search
@@ -63,6 +63,9 @@ In Position. The step axis moves during the run-out and turnaround of the fast a
|
||||
soon as the fast axis has left the exposure range, the PSO window of the next line is set
|
||||
and the step axis starts; the next line starts when both have arrived.
|
||||
|
||||
minimal_run_up=True reduces the run-up / run-out to the ramp distance alone (no margin, no
|
||||
settle_time; at least MIN_RUN_UP = 0.01 so the axis never turns at a PSO window edge), for data analysis that works with unevenly spaced (captured) positions.
|
||||
|
||||
settle_time stays 0.15 s also at high speed: at 20 mm/s / 500 mm/s^2 shorter values
|
||||
(0.08 / 0.04 / 0.02 / 0 s) gave backward - forward offsets of -24 / +10 / +4.5 / -21 um,
|
||||
a ~10 Hz oscillation after the ramp that decays within ~0.15 s (measured 2026-10-29).
|
||||
@@ -110,6 +113,12 @@ DDG_TRIGGER_EDGES = {"rising": DDG_EXT_RISING_EDGE, "falling": DDG_EXT_FALLING_E
|
||||
# servo following error at the end of the ramp)
|
||||
RAMP_MARGIN_FRACTION = 1.0
|
||||
|
||||
# Smallest run-up / run-out with minimal_run_up, in units of the fast axis: at low speed the
|
||||
# ramp distance is sub-um (0.5 mm/s, 500 mm/s^2: 0.25 um), and the fast axis would stop and
|
||||
# reverse right at the edge of the next line's PSO window, where ringing or overshoot could
|
||||
# start the line before the axis moves.
|
||||
MIN_RUN_UP = 0.01
|
||||
|
||||
TRIGGER_SOURCES = ("ddg", "panda", None)
|
||||
|
||||
# Poll interval of the fast axis position while waiting for the end of the exposures, s
|
||||
@@ -153,6 +162,7 @@ class AerotechContGrid(ScanBase):
|
||||
"exp_time",
|
||||
"relative",
|
||||
"trigger_source",
|
||||
"minimal_run_up",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -174,6 +184,7 @@ class AerotechContGrid(ScanBase):
|
||||
trigger_device: str = "pandatrig",
|
||||
shutter_delay: float | None = None,
|
||||
settle_time: float = 0.15,
|
||||
minimal_run_up: bool = False,
|
||||
capture_device: str = "aerocapture",
|
||||
**kwargs,
|
||||
# fmt: on
|
||||
@@ -198,6 +209,7 @@ class AerotechContGrid(ScanBase):
|
||||
trigger_device (str): Name of the PandaLineTrigger device for trigger_source="panda". Default is "pandatrig".
|
||||
shutter_delay (float | None): Delay from the line trigger to the first exposure in s. Default: DDG1 shutter delay for "ddg", 0 otherwise.
|
||||
settle_time (float): Run-up at constant velocity before the trigger position, in s. Default is 0.15.
|
||||
minimal_run_up (bool): Run-up / run-out only the ramp distance v^2 / (2 * acceleration), at least 0.01, without the 2x margin and settle_time: faster lines, but the exposures start right after the ramp while the fast axis may still oscillate, so the captured positions are less evenly spaced. Default is False.
|
||||
capture_device (str): Name of the A3200PositionCapture device (always used). Default is "aerocapture".
|
||||
|
||||
Returns:
|
||||
@@ -253,6 +265,7 @@ class AerotechContGrid(ScanBase):
|
||||
self.trigger_source = trigger_source
|
||||
self.shutter_delay = shutter_delay
|
||||
self.settle_time = settle_time
|
||||
self.minimal_run_up = minimal_run_up
|
||||
|
||||
devices = self.device_manager.devices
|
||||
self.ddg1 = self.ddg2 = self.mcs = self.panda = None
|
||||
@@ -530,7 +543,8 @@ class AerotechContGrid(ScanBase):
|
||||
fwd, bwd = dev[0::2], dev[1::2]
|
||||
text = [
|
||||
f"aerotech_cont_grid statistics ({len(lines)} lines x {n} points, v"
|
||||
f" {self._line['target_velocity']:g} / s, readout {self._line['readout_time'] * 1e3:g} ms):",
|
||||
f" {self._line['target_velocity']:g} / s, readout {self._line['readout_time'] * 1e3:g} ms"
|
||||
f"{', minimal run-up' if self.minimal_run_up else ''}):",
|
||||
f" centre - nominal cell centre [um]: forward mean {fwd.mean():+.2f} rms"
|
||||
f" {fwd.std():.2f} max {np.abs(fwd).max():.2f}",
|
||||
]
|
||||
@@ -632,7 +646,11 @@ class AerotechContGrid(ScanBase):
|
||||
" run-up distance of the line."
|
||||
)
|
||||
ramp_distance = velocity**2 / (2 * line["acceleration"])
|
||||
margin = RAMP_MARGIN_FRACTION * ramp_distance + velocity * self.settle_time
|
||||
if self.minimal_run_up:
|
||||
# exposures start right after the ramp; the captured positions show the result
|
||||
margin = max(MIN_RUN_UP - ramp_distance, 0.0)
|
||||
else:
|
||||
margin = RAMP_MARGIN_FRACTION * ramp_distance + velocity * self.settle_time
|
||||
lead = velocity * line["shutter_open_delay"]
|
||||
# centre each gate (exp_time - readout_time) in its cell: same centres in both directions
|
||||
gap = velocity * line["readout_time"] / 2
|
||||
@@ -660,12 +678,18 @@ class AerotechContGrid(ScanBase):
|
||||
beyond = f"beyond the high limit {high} by {line['line_end'] - high:.4f}"
|
||||
else:
|
||||
beyond = f"beyond the low limit {low} by {low - line['line_start']:.4f}"
|
||||
if self.minimal_run_up:
|
||||
why = f"(acceleration {line['acceleration']:g} / s^2, minimal run-up)"
|
||||
else:
|
||||
why = (
|
||||
f"and settle (acceleration {line['acceleration']:g} / s^2, settle_time"
|
||||
f" {self.settle_time:g} s)"
|
||||
)
|
||||
raise LimitError(
|
||||
f"The scan range of {name} {self.fast_start:.4f} .. {self.fast_end:.4f} is"
|
||||
f" within the limits, but {name} needs {extra:.4f} extra travel on each side"
|
||||
f" to accelerate to the scan velocity {line['target_velocity']:g} / s and"
|
||||
f" settle (acceleration {line['acceleration']:g} / s^2, settle_time"
|
||||
f" {self.settle_time:g} s). It would travel {line['line_start']:.4f} .."
|
||||
f" to accelerate to the scan velocity {line['target_velocity']:g} / s"
|
||||
f" {why}. It would travel {line['line_start']:.4f} .."
|
||||
f" {line['line_end']:.4f}, {beyond}. Move the scan away from the limit or"
|
||||
" scan slower (larger exp_time or smaller fast_step_size)."
|
||||
)
|
||||
|
||||
@@ -530,3 +530,36 @@ def test_scan_calls_only_user_access_methods(attribute, device_cls):
|
||||
signals = {"motor_velocity", "wait_in_position", "max_scan_velocity", "motor_acceleration"}
|
||||
assert called, f"no calls on self.{attribute} found"
|
||||
assert not (called - signals) - set(cls.USER_ACCESS)
|
||||
|
||||
|
||||
def test_minimal_run_up_is_the_ramp_distance_only(v4_scan_assembler, device_manager):
|
||||
scan = _assemble(v4_scan_assembler, device_manager, settle_time=0.1, minimal_run_up=True)
|
||||
scan.prepare_scan()
|
||||
line = scan._line
|
||||
ramp = (0.5 / 0.1) ** 2 / (2 * 100.0)
|
||||
assert ramp > 0.01
|
||||
assert line["line_start"] == pytest.approx(line["trigger_low"] - ramp)
|
||||
assert line["line_end"] == pytest.approx(line["trigger_high"] + ramp)
|
||||
|
||||
|
||||
def test_minimal_run_up_has_a_floor_at_low_speed(v4_scan_assembler, device_manager):
|
||||
# 0.5 mm/s at 100 mm/s^2: ramp 1.25 um, run-up raised to 10 um
|
||||
_add_devices(device_manager)
|
||||
scan = v4_scan_assembler(
|
||||
"aerotech_cont_grid", "aerox", -1.0, 1.0, 0.01, "aeroy", -2.0, 2.0, 2,
|
||||
exp_time=0.02, minimal_run_up=True,
|
||||
)
|
||||
scan.prepare_scan()
|
||||
line = scan._line
|
||||
assert line["trigger_low"] - line["line_start"] == pytest.approx(0.01)
|
||||
assert line["line_end"] - line["trigger_high"] == pytest.approx(0.01)
|
||||
|
||||
|
||||
def test_minimal_run_up_limit_message(v4_scan_assembler, device_manager):
|
||||
_add_devices(device_manager, max_scan_velocity=50.0)
|
||||
scan = v4_scan_assembler(
|
||||
"aerotech_cont_grid", "aerox", -9.5, 9.5, 0.1, "aeroy", -2.0, 2.0, 2,
|
||||
exp_time=0.005, minimal_run_up=True,
|
||||
)
|
||||
with pytest.raises(LimitError, match="minimal run-up"):
|
||||
scan.prepare_scan()
|
||||
Reference in new issue
Block a user