feat/skip lfzp_in/losa_in/losa_out moves when already in position
Ports flomni's ffzp_in()/fosa_in() "skip the move (and, for the FZP, the expensive feedback-disable + reset-enable cycle) if already there" optimization to lamni's lfzp_in()/losa_in(). Also adds the same skip-if-already-there check to losa_out(), which has no flomni equivalent to mirror (fosa_out() always moves unconditionally there) -- added as a lamni-side enhancement per discussion. Checked every existing caller of lfzp_in()/loptics_in() before making this change, since a skip changes behavior for callers that used to get an unconditional reset. Found one real risk: x_ray_eye_align.py's "Step 0: FZP centre" (the start of a fresh alignment run) had its manual feedback disable/enable calls already commented out, meaning it relied entirely on lfzp_in()'s old unconditional reset to establish a correct interferometer zero. flomni's equivalent call site doesn't have this problem because flomni's alignment flow does its own explicit feedback_enable_with_reset() independently beforehand -- lamni's doesn't. Fixed by passing force_feedback_reset=True at that one call site so it keeps its current always-reset behavior; no other lamni caller needed this. Item 4 of csaxs_bec/bec_ipython_client/plugins/LamNI/AI_docs/ FLOMNI_LAMNI_FEATURE_GAPS_2026-07.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
"""Tests for lamni's "skip if already in position" optics optimization,
|
||||
ported from flomni's ffzp_in()/fosa_in() (see csaxs_bec/bec_ipython_client/
|
||||
plugins/LamNI/AI_docs/FLOMNI_LAMNI_FEATURE_GAPS_2026-07.md, item 4).
|
||||
|
||||
Methods under test (lamni_optics_mixin.py) reference module-level `dev`/
|
||||
`umv` globals (resolved from this module's own namespace, not an instance
|
||||
attribute), so tests monkeypatch those module globals directly.
|
||||
"""
|
||||
|
||||
import types
|
||||
|
||||
import pytest
|
||||
|
||||
import csaxs_bec.bec_ipython_client.plugins.LamNI.lamni_optics_mixin as optics_mixin_module
|
||||
from csaxs_bec.bec_ipython_client.plugins.LamNI.lamni_optics_mixin import LamNIOpticsMixin
|
||||
|
||||
|
||||
class FakeAxis:
|
||||
def __init__(self, value, in_pos=None, out_pos=None):
|
||||
self.value = value
|
||||
self.readback = types.SimpleNamespace(get=lambda: self.value)
|
||||
self.user_parameter = {}
|
||||
if in_pos is not None:
|
||||
self.user_parameter["in"] = in_pos
|
||||
if out_pos is not None:
|
||||
self.user_parameter["out"] = out_pos
|
||||
|
||||
|
||||
class FakeController:
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
|
||||
def feedback_disable(self):
|
||||
self.calls.append("disable")
|
||||
|
||||
def feedback_enable_with_reset(self):
|
||||
self.calls.append("enable_with_reset")
|
||||
|
||||
|
||||
class FakeDev:
|
||||
def __init__(self, rtx_enabled=True):
|
||||
self.loptx = FakeAxis(-0.5, in_pos=-0.5, out_pos=-1.0)
|
||||
self.lopty = FakeAxis(3.3, in_pos=3.3, out_pos=3.0)
|
||||
self.losax = FakeAxis(-1.0, in_pos=-1.0, out_pos=0.0)
|
||||
self.losay = FakeAxis(-0.2, in_pos=-0.2, out_pos=1.0)
|
||||
self.losaz = FakeAxis(1.0, in_pos=1.0, out_pos=-1.0)
|
||||
self.rtx = types.SimpleNamespace(enabled=rtx_enabled, controller=FakeController())
|
||||
|
||||
def __contains__(self, name):
|
||||
return hasattr(self, name)
|
||||
|
||||
def __getitem__(self, name):
|
||||
return getattr(self, name)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_dev(monkeypatch):
|
||||
dev = FakeDev()
|
||||
monkeypatch.setattr(optics_mixin_module, "dev", dev)
|
||||
return dev
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def umv_calls(monkeypatch):
|
||||
calls = []
|
||||
monkeypatch.setattr(optics_mixin_module, "umv", lambda *args: calls.append(args))
|
||||
return calls
|
||||
|
||||
|
||||
def make_optics():
|
||||
return LamNIOpticsMixin()
|
||||
|
||||
|
||||
def test_lfzp_in_skips_move_when_already_in_position(fake_dev, umv_calls, capsys):
|
||||
optics = make_optics()
|
||||
|
||||
optics.lfzp_in()
|
||||
|
||||
assert umv_calls == []
|
||||
assert "already at the in position" in capsys.readouterr().out
|
||||
assert fake_dev.rtx.controller.calls == []
|
||||
|
||||
|
||||
def test_lfzp_in_moves_and_resets_feedback_when_out_of_position(fake_dev, umv_calls):
|
||||
fake_dev.loptx.value = -0.9 # not at "in" (-0.5)
|
||||
optics = make_optics()
|
||||
|
||||
optics.lfzp_in()
|
||||
|
||||
assert umv_calls == [(fake_dev.loptx, -0.5, fake_dev.lopty, 3.3)]
|
||||
assert fake_dev.rtx.controller.calls == ["disable", "enable_with_reset"]
|
||||
|
||||
|
||||
def test_lfzp_in_force_feedback_reset_runs_cycle_even_if_already_in(fake_dev, umv_calls):
|
||||
optics = make_optics()
|
||||
|
||||
optics.lfzp_in(force_feedback_reset=True)
|
||||
|
||||
assert umv_calls == [] # still no physical move needed
|
||||
assert fake_dev.rtx.controller.calls == ["disable", "enable_with_reset"]
|
||||
|
||||
|
||||
def test_losa_in_skips_move_when_already_in_position(fake_dev, umv_calls, capsys):
|
||||
optics = make_optics()
|
||||
|
||||
optics.losa_in()
|
||||
|
||||
assert umv_calls == []
|
||||
assert "already at the IN position" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_losa_in_moves_when_out_of_position(fake_dev, umv_calls):
|
||||
fake_dev.losaz.value = 5.0 # not at "in" (1.0)
|
||||
optics = make_optics()
|
||||
|
||||
optics.losa_in()
|
||||
|
||||
assert umv_calls == [
|
||||
(fake_dev.losax, -1.0, fake_dev.losay, -0.2),
|
||||
(fake_dev.losaz, 1.0),
|
||||
]
|
||||
|
||||
|
||||
def test_losa_out_skips_move_when_already_out(fake_dev, umv_calls, capsys):
|
||||
fake_dev.losay.value = 1.0 # matches "out"
|
||||
fake_dev.losaz.value = -1.0 # matches "out"
|
||||
optics = make_optics()
|
||||
|
||||
optics.losa_out()
|
||||
|
||||
assert umv_calls == []
|
||||
assert "already at the OUT position" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_losa_out_moves_when_not_out(fake_dev, umv_calls):
|
||||
optics = make_optics() # currently at "in" positions, not "out"
|
||||
|
||||
optics.losa_out()
|
||||
|
||||
assert umv_calls == [
|
||||
(fake_dev.losaz, -1.0),
|
||||
(fake_dev.losay, 1.0),
|
||||
]
|
||||
Reference in New Issue
Block a user