Skip to content

Commit

Permalink
Merge pull request #36 from qua-platform/feat/input-channels
Browse files Browse the repository at this point in the history
Feat/Add input channels
  • Loading branch information
nulinspiratie authored May 13, 2024
2 parents 5d3e688 + c834bc9 commit 29ac231
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 92 deletions.
204 changes: 164 additions & 40 deletions quam/components/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@
"DigitalOutputChannel",
"Channel",
"SingleChannel",
"InOutSingleChannel",
"InSingleChannel",
"IQChannel",
"InIQChannel",
"InOutSingleChannel",
"InOutIQChannel",
"InSingleOutIQChannel",
"InIQOutSingleChannel",
]


Expand Down Expand Up @@ -471,25 +475,23 @@ def apply_to_config(self, config: dict):


@quam_dataclass
class InOutSingleChannel(SingleChannel):
"""QuAM component for a single (not IQ) input & output channel.
class InSingleChannel(Channel):
"""QuAM component for a single (not IQ) input channel.
Args:
operations (Dict[str, Pulse]): A dictionary of pulses to be played on this
channel. The key is the pulse label (e.g. "X90") and value is a Pulse.
id (str, int): The id of the channel, used to generate the name.
Can be a string, or an integer in which case it will add
`Channel._default_label`.
opx_output (Tuple[str, int]): Channel output port from OPX perspective,
a tuple of (controller_name, port).
opx_input (Tuple[str, int]): Channel input port from OPX perspective,
a tuple of (controller_name, port).
filter_fir_taps (List[float]): FIR filter taps for the output port.
filter_iir_taps (List[float]): IIR filter taps for the output port.
opx_output_offset (float): DC offset for the output port.
opx_input_offset (float): DC offset for the input port.
intermediate_frequency (float): Intermediate frequency of OPX output, default
is None.
intermediate_frequency (float): Intermediate frequency of OPX input,
default is None.
time_of_flight (int): Round-trip signal duration in nanoseconds.
smearing (int): Additional window of ADC integration in nanoseconds.
Used to account for signal smearing.
"""

opx_input: Tuple[str, int]
Expand All @@ -499,7 +501,7 @@ class InOutSingleChannel(SingleChannel):
smearing: int = 0

def apply_to_config(self, config: dict):
"""Adds this SingleChannel to the QUA configuration.
"""Adds this InSingleChannel to the QUA configuration.
See [`QuamComponent.apply_to_config`][quam.core.quam_classes.QuamComponent.apply_to_config]
for details.
Expand Down Expand Up @@ -879,35 +881,27 @@ def apply_to_config(self, config: dict):


@quam_dataclass
class InOutIQChannel(IQChannel):
"""QuAM component for an IQ channel with both input and output.
An example of such a channel is a readout resonator, where you may want to
apply a readout tone and then measure the response.
operations (Dict[str, Pulse]): A dictionary of pulses to be played on this
channel. The key is the pulse label (e.g. "readout") and value is a
ReadoutPulse.
id (str, int): The id of the channel, used to generate the name.
Can be a string, or an integer in which case it will add
`Channel._default_label`.
opx_output_I (Tuple[str, int]): Channel I output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_Q (Tuple[str, int]): Channel Q output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_offset_I float: The offset of the I channel. Default is 0.
opx_output_offset_Q float: The offset of the Q channel. Default is 0.
opx_input_I (Tuple[str, int]): Channel I input port from the OPX perspective,
a tuple of (controller_name, port).
opx_input_Q (Tuple[str, int]): Channel Q input port from the OPX perspective,
a tuple of (controller_name, port).
opx_input_offset_I float: The offset of the I channel. Default is 0.
opx_input_offset_Q float: The offset of the Q channel. Default is 0.
intermediate_frequency (float): Intermediate frequency of the mixer.
frequency_converter_up (FrequencyConverter): Frequency converter QuAM component
for the IQ output.
frequency_converter_down (Optional[FrequencyConverter]): Frequency converter
QuAM component for the IQ input port. Only needed for the old Octave.
class InIQChannel(Channel):
"""QuAM component for an IQ input channel
operations (Dict[str, Pulse]): A dictionary of pulses to be played on this
channel. The key is the pulse label (e.g. "readout") and value is a
ReadoutPulse.
id (str, int): The id of the channel, used to generate the name.
Can be a string, or an integer in which case it will add
`Channel._default_label`.
opx_input_I (Tuple[str, int]): Channel I input port from the OPX perspective,
a tuple of (controller_name, port).
opx_input_Q (Tuple[str, int]): Channel Q input port from the OPX perspective,
a tuple of (controller_name, port).
opx_input_offset_I float: The offset of the I channel. Default is 0.
opx_input_offset_Q float: The offset of the Q channel. Default is 0.
frequency_converter_down (Optional[FrequencyConverter]): Frequency converter
QuAM component for the IQ input port. Only needed for the old Octave.
time_of_flight (int): Round-trip signal duration in nanoseconds.
smearing (int): Additional window of ADC integration in nanoseconds.
Used to account for signal smearing.
input_gain (float): The gain of the input channel. Default is None.
"""

opx_input_I: Tuple[str, int]
Expand Down Expand Up @@ -1219,3 +1213,133 @@ def measure_sliced(
),
)
return tuple(qua_vars)


@quam_dataclass
class InOutSingleChannel(SingleChannel, InSingleChannel):
"""QuAM component for a single (not IQ) input + output channel.
Args:
operations (Dict[str, Pulse]): A dictionary of pulses to be played on this
channel. The key is the pulse label (e.g. "X90") and value is a Pulse.
id (str, int): The id of the channel, used to generate the name.
Can be a string, or an integer in which case it will add
`Channel._default_label`.
opx_output (Tuple[str, int]): Channel output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_offset (float): DC offset for the output port.
opx_input (Tuple[str, int]): Channel input port from OPX perspective,
a tuple of (controller_name, port).
opx_input_offset (float): DC offset for the input port.
filter_fir_taps (List[float]): FIR filter taps for the output port.
filter_iir_taps (List[float]): IIR filter taps for the output port.
intermediate_frequency (float): Intermediate frequency of OPX output, default
is None.
time_of_flight (int): Round-trip signal duration in nanoseconds.
smearing (int): Additional window of ADC integration in nanoseconds.
Used to account for signal smearing.
"""

pass


@quam_dataclass
class InOutIQChannel(IQChannel, InIQChannel):
"""QuAM component for an IQ channel with both input and output.
An example of such a channel is a readout resonator, where you may want to
apply a readout tone and then measure the response.
Args:
operations (Dict[str, Pulse]): A dictionary of pulses to be played on this
channel. The key is the pulse label (e.g. "readout") and value is a
ReadoutPulse.
id (str, int): The id of the channel, used to generate the name.
Can be a string, or an integer in which case it will add
`Channel._default_label`.
opx_output_I (Tuple[str, int]): Channel I output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_Q (Tuple[str, int]): Channel Q output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_offset_I float: The offset of the I channel. Default is 0.
opx_output_offset_Q float: The offset of the Q channel. Default is 0.
opx_input_I (Tuple[str, int]): Channel I input port from the OPX perspective,
a tuple of (controller_name, port).
opx_input_Q (Tuple[str, int]): Channel Q input port from the OPX perspective,
a tuple of (controller_name, port).
opx_input_offset_I float: The offset of the I channel. Default is 0.
opx_input_offset_Q float: The offset of the Q channel. Default is 0.
intermediate_frequency (float): Intermediate frequency of the mixer.
frequency_converter_up (FrequencyConverter): Frequency converter QuAM component
for the IQ output.
frequency_converter_down (Optional[FrequencyConverter]): Frequency converter
QuAM component for the IQ input port. Only needed for the old Octave.
time_of_flight (int): Round-trip signal duration in nanoseconds.
smearing (int): Additional window of ADC integration in nanoseconds.
Used to account for signal smearing.
"""

pass


@quam_dataclass
class InSingleOutIQChannel(IQChannel, InSingleChannel):
"""QuAM component for an IQ output channel with a single input.
Args:
operations (Dict[str, Pulse]): A dictionary of pulses to be played on this
channel. The key is the pulse label (e.g. "readout") and value is a
ReadoutPulse.
id (str, int): The id of the channel, used to generate the name.
Can be a string, or an integer in which case it will add
`Channel._default_label`.
opx_output_I (Tuple[str, int]): Channel I output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_Q (Tuple[str, int]): Channel Q output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_offset_I float: The offset of the I channel. Default is 0.
opx_output_offset_Q float: The offset of the Q channel. Default is 0.
opx_input (Tuple[str, int]): Channel input port from OPX perspective,
a tuple of (controller_name, port).
opx_input_offset (float): DC offset for the input port.
intermediate_frequency (float): Intermediate frequency of the mixer.
frequency_converter_up (FrequencyConverter): Frequency converter QuAM component
for the IQ output.
time_of_flight (int): Round-trip signal duration in nanoseconds.
smearing (int): Additional window of ADC integration in nanoseconds.
Used to account for signal smearing.
"""

pass


@quam_dataclass
class InIQOutSingleChannel(SingleChannel, InIQChannel):
"""QuAM component for an IQ input channel with a single output.
Args:
operations (Dict[str, Pulse]): A dictionary of pulses to be played on this
channel. The key is the pulse label (e.g. "readout") and value is a
ReadoutPulse.
id (str, int): The id of the channel, used to generate the name.
Can be a string, or an integer in which case it will add
`Channel._default_label`.
opx_output (Tuple[str, int]): Channel output port from the OPX perspective,
a tuple of (controller_name, port).
opx_output_offset (float): DC offset for the output port.
opx_input_I (Tuple[str, int]): Channel I input port from the OPX perspective,
a tuple of (controller_name, port).
opx_input_Q (Tuple[str, int]): Channel Q input port from the OPX perspective,
a tuple of (controller_name, port).
opx_input_offset_I float: The offset of the I channel. Default is 0.
opx_input_offset_Q float: The offset of the Q channel. Default is 0.
filter_fir_taps (List[float]): FIR filter taps for the output port.
filter_iir_taps (List[float]): IIR filter taps for the output port.
intermediate_frequency (float): Intermediate frequency of OPX output, default
is None.
time_of_flight (int): Round-trip signal duration in nanoseconds.
smearing (int): Additional window of ADC integration in nanoseconds.
Used to account for signal smearing.
"""

pass
17 changes: 10 additions & 7 deletions quam/utils/dataclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import functools
import sys
import warnings
from typing import Dict, Union, get_type_hints
from typing import Dict, Union, ClassVar, get_type_hints


__all__ = ["patch_dataclass", "get_dataclass_attr_annotations"]
Expand Down Expand Up @@ -44,6 +44,8 @@ def get_dataclass_attr_annotations(

attr_annotations = {"required": {}, "optional": {}}
for attr, attr_type in annotated_attrs.items():
if getattr(attr_type, "__origin__", None) == ClassVar:
continue
# TODO Try to combine with third elif statement
if getattr(cls_or_obj, attr, None) is REQUIRED: # See "patch_dataclass()"
if attr not in getattr(cls_or_obj, "__dataclass_fields__", {}):
Expand Down Expand Up @@ -101,12 +103,13 @@ def handle_inherited_required_fields(cls):
return

# Check if class (not parents) has required fields
required_attrs = [
attr
for attr in cls.__annotations__
if attr not in cls.__dict__ and attr not in optional_fields
]
for attr in required_attrs:
for attr, attr_type in cls.__annotations__.items():
if attr in cls.__dict__:
continue
if attr in optional_fields:
continue
if getattr(attr_type, "__origin__", None) is ClassVar:
continue
setattr(cls, attr, REQUIRED)


Expand Down
22 changes: 9 additions & 13 deletions tests/components/channels/test_digital_channel.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from copy import deepcopy
from quam.components import Channel, DigitalOutputChannel, pulses
from quam.core import QuamRoot, quam_dataclass
from quam.core.qua_config_template import qua_config_template
from quam.core.quam_instantiation import instantiate_quam_class


Expand All @@ -10,7 +9,7 @@ class QuamTest(QuamRoot):
channel: Channel


def test_digital_only_channel():
def test_digital_only_channel(qua_config):
channel = Channel(
id="channel",
digital_outputs={"1": DigitalOutputChannel(opx_output=("con1", 1))},
Expand All @@ -19,16 +18,15 @@ def test_digital_only_channel():
quam = QuamTest(channel=channel)
cfg = quam.generate_config()

expected_cfg = deepcopy(qua_config_template)
expected_cfg["controllers"] = {"con1": {"digital_outputs": {1: {}}}}
expected_cfg["elements"] = {
qua_config["controllers"] = {"con1": {"digital_outputs": {1: {}}}}
qua_config["elements"] = {
"channel": {"digitalInputs": {"1": {"port": ("con1", 1)}}, "operations": {}}
}

assert cfg == expected_cfg
assert cfg == qua_config


def test_digital_only_pulse():
def test_digital_only_pulse(qua_config):
channel = Channel(
id="channel",
operations={
Expand All @@ -40,21 +38,19 @@ def test_digital_only_pulse():
quam = QuamTest(channel=channel)
cfg = quam.generate_config()

expected_cfg = deepcopy(qua_config_template)
# expected_cfg["controllers"] = {"con1": {"digital_outputs": {1: {}}}}
expected_cfg["elements"] = {
qua_config["elements"] = {
"channel": {"operations": {"digital": "channel.digital.pulse"}}
}
expected_cfg["pulses"]["channel.digital.pulse"] = {
qua_config["pulses"]["channel.digital.pulse"] = {
"length": 100,
"operation": "control",
"digital_marker": "channel.digital.dm",
}
expected_cfg["digital_waveforms"]["channel.digital.dm"] = {
qua_config["digital_waveforms"]["channel.digital.dm"] = {
"samples": [(1, 20, 0, 10)]
}

assert cfg == expected_cfg
assert cfg == qua_config


def test_instantiate_digital_channel():
Expand Down
Loading

0 comments on commit 29ac231

Please sign in to comment.