from __future__ import annotations
from atomiq.components.primitives import Component, Parametrizable
from atomiq.components.electronics.rfsource import RFSource
from artiq.experiment import kernel, portable, delay
from artiq.language.types import TFloat, TInt32, TBool
from artiq.language.units import s
[docs]
class Lock(Component):
"""
This is a very generic class to describe a laser lock. It is characterized by some means
of a reference frequency and an offset to that frequency at which the lock tries to stabilize.
Most likely you want to use a more specific class inherited from this one.
Args:
reference_frequency: The frequency in Hz that the lock references to
lock_offset: The frequency offset in Hz at which the lock stabilizes
harmonic: Use this option if the laser is frequency converted but locked to the fundamental. Use 1 for no
conversion, 2 for SHG, 4 for FHG ,... (default 1)
"""
kernel_invariants = {"harmonic"}
def __init__(self, reference_frequency: TFloat, lock_offset: TFloat = 0.0, harmonic: TInt32 = 1, *args, **kwargs):
Component.__init__(self, *args, **kwargs)
self._lock_offset = lock_offset
self.reference_frequency = reference_frequency
self.harmonic = harmonic
[docs]
@portable
def get_frequency(self) -> TFloat:
return self.reference_frequency + self._lock_offset
[docs]
class DetunableLock(Lock, Parametrizable):
"""
This is a very generic class to describe a laser lock where the lock offset can be
changed. It is characterized by some means of a reference frequency and a detunable
offset to that frequency at which the lock tries to stabilize.
Most likely you want to use a more specific class inherited from this one.
Args:
settle_time: Time in s the lock needs to settle after a detuning of the lock point
"""
kernel_invariants = {"settle_time"}
def __init__(self, settle_time: TFloat = 0, blind: TBool = False, *args, **kwargs):
Lock.__init__(self, *args, **kwargs)
Parametrizable.__init__(self, ["frequency", "detuning"])
self.settle_time = settle_time
@kernel
def do_nothing(self):
pass
if blind:
self._prerun = do_nothing.__get__(self)
@kernel
def _prerun(self):
self.set_detuning(self._lock_offset)
@portable
def _set_lock_frequency(self, detuning: TFloat):
raise NotImplementedError("Abstract class DetunableLaser cannot set lock frequency. Implement it in a subclass")
[docs]
@portable
def set_frequency(self, frequency: TFloat):
"""Set the absolute frequency of the locked line
Args:
frequency: The absoulte frequency at which the system should lock
"""
self.set_detuning(frequency - self.reference_frequency)
[docs]
@portable
def set_detuning(self, offset_frequency: TFloat):
"""
Set the frequency relative to the reference frequency, i.e. set the lock offset.
Args:
offset_frequency: new lock offset in Hz
"""
self._lock_offset = offset_frequency
self.experiment.log.info(self.identifier + ": New lock offset {0:.3f} MHz", [self._lock_offset/1e6])
self._set_lock_frequency(offset_frequency/self.harmonic)
# wait for the lock to settle
delay(self.settle_time*s)
[docs]
class SpectroscopyLock(Lock):
pass
[docs]
class SidebandLock(DetunableLock):
"""Lock on a sideband modulated onto the laser
This kind of lock is frequently used when locking on tunable sideband to the transmission
signal of a stable cavity. The offset frequency for the sideband is generated by an RF source
and can be changed at runtime. Thus the lock point can be detuned.
Args:
rf_source: RF source that determines the sideband frequency.
sideband_order: Order of the sideband the laser is locked to [.., -2, -1, 1, 2, ...] (default 1)
"""
kernel_invariants = {"rf_source", "sideband_order"}
def __init__(self, rf_source: RFSource, sideband_order: TInt32 = 1, *args, **kwargs):
self.rf_source = rf_source
self.sideband_order = sideband_order
DetunableLock.__init__(self, *args, **kwargs)
@portable
def _set_lock_frequency(self, frequency: TFloat):
self.rf_source.set_frequency(frequency/self.sideband_order)
[docs]
class OFCLock(DetunableLock):
"""Lock on an optical frequency comb
This is used to lock the laser on a beat note with an optical frequency comb. It is characterized by the
frequency of the closest comb tooth and the offset frequency. The offset frequency is generated by an RF source
and can be changed at runtime. Thus the lock point can be detuned.
Args:
rfsource: RF source that generates the reference beat frequency (i.e. the offset frequency) to which the laser
beat note is stabilzed.
reference_frequency: The frequency in Hz of the comb tooth. Instead (tooth_number, rep_rate, ceo) can be given
to automatically calculate the reference frequency
lock_offset: The default frequency offset to the comb tooth in Hz at which the lock stabilizes
settle_time: Time in s the lock needs to settle after a detuning of the lock point
lock_direction: Whether the laser is to the positive (+1) or negative (-1) beat note (default 1)
tooth_number: Number of the comb tooth, the laser is beaten with (default -1)
rep_rate: Repetition frequency of the comb in Hz (default -1)
ceo: Ceo frequency of the comb in Hz (default -1)
"""
kernel_invariants = {"rf_source", "lock_direction"}
def __init__(self,
rf_source: RFSource,
lock_direction: TInt32 = 1,
tooth_number: TInt32 = -1,
rep_rate: TFloat = -1,
ceo: TFloat = -1,
*args, **kwargs):
self.rf_source = rf_source
self.lock_direction = lock_direction
if ceo > 0 and rep_rate > 0 and tooth_number > 0:
kwargs["reference_frequency"] = tooth_number*rep_rate + ceo
DetunableLock.__init__(self, *args, **kwargs)
@portable
def _set_lock_frequency(self, frequency: TFloat):
self.rf_source.set_frequency(frequency/self.lock_direction)
[docs]
class OffsetLock(SidebandLock):
"""Lock on a reference laser via beating signal
Args:
reference_laser: laser that is used to generate the beat note. It's frequency serves as the reference frequency
for this lock
"""
kernel_invariants = {"reference_laser"}
def __init__(self, reference_laser, *args, **kwargs):
self.reference_laser = reference_laser
kwargs["reference_frequency"] = self.reference_laser.get_frequency()
SidebandLock.__init__(self, *args, **kwargs)
[docs]
@portable
def get_frequency(self) -> TFloat:
return self.reference_laser.get_frequency() + self._lock_offset
[docs]
@portable
def set_frequency(self, frequency: TFloat):
self.set_detuning(self.harmonic*(frequency - self.reference_laser.get_frequency()))