from __future__ import annotations
from atomiq.components.primitives import Component, Parametrizable
from artiq.experiment import kernel
from artiq.language.types import TFloat, TInt32, TBool
from artiq.language.core import delay
[docs]
class VoltageSource(Component, Parametrizable):
"""Voltage Source
This abstract class represents any device that can output a defined, controllable voltage.
Args:
min_voltage: The minimum voltage the device can output [V]
max_voltage: The maximum voltage the device can output [V]
default_ramp_steps: The default number of steps that this device should use if the voltage is ramped. This
value is only used if no `ramp_steps` are given in the :func:`ramp_voltage` method.
"""
kernel_invariants = {"min_voltage", "max_voltage", "default_ramp_steps"}
def __init__(self,
min_voltage: TFloat = float('-inf'),
max_voltage: TFloat = float('inf'),
default_ramp_steps: TInt32 = 30,
*args, **kwargs):
Component.__init__(self, *args, **kwargs)
Parametrizable.__init__(self, ["voltage"])
self.min_voltage = min_voltage
self.max_voltage = max_voltage
self.default_ramp_steps = default_ramp_steps
[docs]
@kernel
def set_voltage(self, voltage: TFloat):
"""
Set the voltage delivered by the voltage source
Args:
voltage: Voltage in V
"""
if voltage >= self.min_voltage and voltage <= self.max_voltage:
self._set_voltage(voltage)
else:
self.experiment.log.warning(self.identifier +
": Trying to set voltage {0}V which is out of limits [{1}V, {2}V]",
[voltage, self.min_voltage, self.max_voltage])
@kernel
def _set_voltage(self, voltage: TFloat):
raise NotImplementedError("Implement `set_voltage()` for your voltage source")
@kernel(flags={"fast-math"})
def _ramp_voltage(self,
duration: TFloat,
voltage_start: TFloat,
voltage_end: TFloat,
ramp_timestep: TFloat = 200e-6):
"""
This method implements a stupid ramp on an abstract level. This will most likely work but be slow.
If your hardware has native support for ramping, please override this function when you inherit from
VoltageSource
"""
n = int(duration / ramp_timestep)
for i in range(n + 1):
self._set_voltage(voltage_start + i * (voltage_end - voltage_start) / n)
delay(ramp_timestep)
[docs]
@kernel(flags={"fast-math"})
def ramp_voltage(self,
duration: TFloat,
voltage_start: TFloat,
voltage_end: TFloat,
ramp_timestep: TFloat = -1.0,
ramp_steps: TInt32 = -1):
"""Ramp voltage over a given duration.
This method advances the timeline by `duration`
Args:
duration: ramp duration [s]
voltage_start: initial voltage [V]
voltage_end: end voltage [V]
"""
if self.min_voltage <= voltage_start <= self.max_voltage \
and self.min_voltage <= voltage_end <= self.max_voltage:
if ramp_timestep > 0:
pass
elif ramp_steps > 0:
ramp_timestep = duration/ramp_steps
else:
ramp_timestep = duration/self.default_ramp_steps
self._ramp_voltage(duration, voltage_start, voltage_end, ramp_timestep)
else:
self.experiment.log.warning(self.identifier +
": Trying to ramp voltage {0}V -> {1}V which is out of limits [{2}V, {3}V]",
[voltage_start, voltage_end, self.min_voltage, self.max_voltage])
[docs]
class DAC(Component, Parametrizable):
"""A DAC with multiple channels
Args:
num_chan: Number of channels the DAC has
"""
kernel_invariants = {"num_chan"}
def __init__(self, num_chan: TInt32, *args, **kwargs):
Component.__init__(self, *args, **kwargs)
Parametrizable.__init__(self, [f"voltage{i}" for i in range(num_chan)])
self.num_chan = num_chan
[docs]
@kernel
def set_channel_voltage(self, channel: TInt32, voltage: TFloat):
pass
[docs]
@kernel
def update(self):
raise NotImplementedError("Implement the update method for your DAC")
[docs]
class DACChannel(VoltageSource):
kernel_invariants = {"dac_device", "channel"}
def __init__(self, dac_device: DAC, channel: TInt32, *args, **kwargs):
"""A single channel of a (possibly multichannel) DAC
A single channel of a DAC is a digitally controlled voltage source. Thus, this class inherits from
:class:`~atomiq.components.electronics.voltagesource.VoltageSource`.
Args:
dac_device: the DAC the channel belongs to
channel: the number of the channel
"""
VoltageSource.__init__(self, *args, **kwargs)
self.dac_device = dac_device
self.channel = channel
@kernel
def _set_voltage(self, voltage: TFloat, update_dac: TBool = True):
self.dac_device.set_channel_voltage(self.channel, voltage)
if update_dac:
self.dac_device.update()