Source code for atomiq.components.lock

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()))