Source code for herosdevices.hardware.menlo.repetition_rate

"""HERO driver for the repetition-rate stabilization loop of a Menlo Systems frequency comb."""

from herosdevices.helper import mark_driver

from .beat_loop import BeatLoop
from .dds import DDS
from .functional_layer import FunctionalLayerModule
from .mixins import CurrentControl, SelectMonitor
from .ofc import OFC


[docs] @mark_driver( name="Menlo Repetition Rate", info="Repetition-rate stabilization loop of a Menlo Systems frequency comb, via QWebChannel", state="alpha", additional_docs=["/tutorials/menlo_ofc.rst"], product_page="https://www.menlosystems.com/", ) class RepetitionRate(DDS, BeatLoop): """Driver for the repetition-rate stabilization loop of a Menlo Systems frequency comb. Wraps the OFC's `functionalLayer.rrSettings` sub-tree, a fixed, singleton module (there is only one repetition-rate loop per OFC, so unlike :py:class:`~herosdevices.hardware.menlo.DDS`/ :py:class:`~herosdevices.hardware.menlo.LaserLock` there is no `funclayer_identifier` to select). Combines :py:class:`~herosdevices.hardware.menlo.DDS` (the loop's underlying DDS, confirmed identical in shape to a laser-lock module's) with :py:class:`BeatLoop` (the shared lock-loop shape). This means `RepetitionRate` implements atomiq's `RFSource` interface via the DDS: `_set_frequency` tunes the DDS directly, not the loop's beat-frequency setpoint (see :py:attr:`beat_frequency_setpoint` for that). Also exposes `select_monitor` (the same node shape seen on CW channels and `CEO`, so treated as a general default here). One observed comb additionally has `amp1542` (laser-diode current control, see `amp_node`), `frequencyDistributionOpticalBeat`/`frequencyDistributionRfBeat`, and an `operationMode.lockMode` single/dual-lock switch - none of those are modeled here, since they weren't seen anywhere else and it's unconfirmed whether they're a fixed feature of every Menlo repetition-rate loop or specific to that comb's configuration. Poll/control them via `observables` or `ofc.get_node`/ `set_node` directly if your comb has them. Args: ofc: The OFC HERO this loop belongs to. amp_node: Relative node name of this loop's laser-diode current-control block (`amp*`), if it has one. `None` (the default) leaves :py:attr:`current_control` unset. observables: Additional observables to poll, merged on top of `DEFAULT_OBSERVABLES`, see :py:class:`~herosdevices.hardware.menlo.functional_layer.FunctionalLayerModule`. """ DEFAULT_OBSERVABLES: dict[str, dict[str, str]] = BeatLoop.DEFAULT_OBSERVABLES | { "beat_frequency": {"path": "repetitionRate.rrCounterRepRate", "unit": "Hz"}, "beat_frequency_setpoint": {"path": "repetitionRate.rrTargetBeatRF", "unit": "Hz"}, "dds_frequency": {"path": "dds.ddsFrequency", "unit": "Hz"}, } def __init__( self, ofc: OFC, amp_node: str | None = None, observables: dict[str, dict[str, str]] | None = None, ) -> None: FunctionalLayerModule.__init__(self, ofc, "functionalLayer.rrSettings", observables=observables) self.select_monitor = SelectMonitor(self, "selectMonitor") self.current_control = CurrentControl(self, amp_node) if amp_node is not None else None @property def beat_frequency(self) -> float: """The currently measured repetition-rate beat frequency. Read-only.""" return self.ofc.get_node(self._path("repetitionRate.rrCounterRepRate")) @property def beat_frequency_setpoint(self) -> float: """Target repetition-rate beat frequency.""" return self.ofc.get_node(self._path("repetitionRate.rrTargetBeatRF")) @beat_frequency_setpoint.setter def beat_frequency_setpoint(self, frequency: float) -> None: self.ofc.set_node(self._path("repetitionRate.rrTargetBeatRF"), frequency)