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)