diff --git a/README.md b/README.md index fd8589b0..6621ae5b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Explore detailed documentation and get started with QuAM here: [QuAM Documentati - **State Management**: Features robust tools for saving and loading your quantum states, promoting reproducibility and consistency. ## Installation -To install QuAM, first ensure you have Python ≥ 3.8 installed on your system. +To install QuAM, first ensure you have 3.8 ≤ Python ≤ 3.11 installed on your system. Then run the following command: ```bash diff --git a/tests/components/channels/test_IQ_channel.py b/tests/components/channels/test_IQ_channel.py index 3c3d06f2..fd1892bd 100644 --- a/tests/components/channels/test_IQ_channel.py +++ b/tests/components/channels/test_IQ_channel.py @@ -1,5 +1,6 @@ import pytest from quam.components import IQChannel +from quam.components.ports.analog_outputs import OPXPlusAnalogOutputPort def test_IQ_channel_set_dc_offset(mocker): @@ -88,3 +89,69 @@ def test_IQ_channel_inferred_LO_frequency(): channel.intermediate_frequency = None with pytest.raises(AttributeError): channel.inferred_LO_frequency + + +def test_generate_config(qua_config): + channel = IQChannel( + id="out_channel", + opx_output_I=("con1", 1), + opx_output_Q=("con1", 2), + frequency_converter_up=None, + ) + + channel.apply_to_config(qua_config) + channel.opx_output_I.apply_to_config(qua_config) + channel.opx_output_Q.apply_to_config(qua_config) + + assert qua_config["controllers"] == { + "con1": { + "analog_outputs": { + 1: {"delay": 0, "shareable": False}, + 2: {"delay": 0, "shareable": False}, + }, + } + } + + assert qua_config["elements"] == { + "out_channel": { + "intermediate_frequency": 0.0, + "mixInputs": { + "I": ("con1", 1), + "Q": ("con1", 2), + }, + "operations": {}, + } + } + + +def test_generate_config_ports(qua_config): + channel = IQChannel( + id="out_channel", + opx_output_I=OPXPlusAnalogOutputPort("con1", 1), + opx_output_Q=OPXPlusAnalogOutputPort("con1", 2), + frequency_converter_up=None, + ) + + channel.apply_to_config(qua_config) + channel.opx_output_I.apply_to_config(qua_config) + channel.opx_output_Q.apply_to_config(qua_config) + + assert qua_config["controllers"] == { + "con1": { + "analog_outputs": { + 1: {"delay": 0, "shareable": False}, + 2: {"delay": 0, "shareable": False}, + }, + } + } + + assert qua_config["elements"] == { + "out_channel": { + "intermediate_frequency": 0.0, + "mixInputs": { + "I": ("con1", 1), + "Q": ("con1", 2), + }, + "operations": {}, + } + } diff --git a/tests/components/channels/test_digital_channel.py b/tests/components/channels/test_digital_channel.py index 652d4049..2b3ed805 100644 --- a/tests/components/channels/test_digital_channel.py +++ b/tests/components/channels/test_digital_channel.py @@ -1,5 +1,6 @@ from copy import deepcopy from quam.components import Channel, DigitalOutputChannel, pulses +from quam.components.ports.digital_outputs import OPXPlusDigitalOutputPort from quam.core import QuamRoot, quam_dataclass from quam.core.quam_instantiation import instantiate_quam_class @@ -18,7 +19,9 @@ def test_digital_only_channel(qua_config): quam = QuamTest(channel=channel) cfg = quam.generate_config() - qua_config["controllers"] = {"con1": {"digital_outputs": {1: {"inverted": False, "shareable": False}}}} + qua_config["controllers"] = { + "con1": {"digital_outputs": {1: {"inverted": False, "shareable": False}}} + } qua_config["elements"] = { "channel": {"digitalInputs": {"1": {"port": ("con1", 1)}}, "operations": {}} } @@ -26,6 +29,32 @@ def test_digital_only_channel(qua_config): assert cfg == qua_config +def test_digital_only_channel_with_port(qua_config): + + channel = Channel( + id="channel", + digital_outputs={ + "1": DigitalOutputChannel( + opx_output=OPXPlusDigitalOutputPort( + "con1", 2, inverted=True, shareable=True + ) + ) + }, + ) + + quam = QuamTest(channel=channel) + cfg = quam.generate_config() + + qua_config["controllers"] = { + "con1": {"digital_outputs": {2: {"inverted": True, "shareable": True}}} + } + qua_config["elements"] = { + "channel": {"digitalInputs": {"1": {"port": ("con1", 2)}}, "operations": {}} + } + + assert cfg == qua_config + + def test_digital_only_pulse(qua_config): channel = Channel( id="channel", diff --git a/tests/components/channels/test_in_IQ_out_single_channel.py b/tests/components/channels/test_in_IQ_out_single_channel.py index cecf7824..3090a922 100644 --- a/tests/components/channels/test_in_IQ_out_single_channel.py +++ b/tests/components/channels/test_in_IQ_out_single_channel.py @@ -1,4 +1,6 @@ from quam.components.channels import InIQOutSingleChannel +from quam.components.ports.analog_inputs import OPXPlusAnalogInputPort +from quam.components.ports.analog_outputs import OPXPlusAnalogOutputPort from quam.utils.dataclass import get_dataclass_attr_annotations @@ -56,3 +58,38 @@ def test_generate_config(qua_config): "time_of_flight": 24, } } + + +def test_generate_config_ports(qua_config): + channel = InIQOutSingleChannel( + id="in_out_channel", + opx_output=OPXPlusAnalogOutputPort("con1", 1), + opx_input_I=OPXPlusAnalogInputPort("con1", 1), + opx_input_Q=OPXPlusAnalogInputPort("con1", 2), + frequency_converter_down=None, + ) + + channel.apply_to_config(qua_config) + channel.opx_output.apply_to_config(qua_config) + channel.opx_input_I.apply_to_config(qua_config) + channel.opx_input_Q.apply_to_config(qua_config) + + assert qua_config["controllers"] == { + "con1": { + "analog_inputs": { + 1: {"gain_db": 0, "shareable": False}, + 2: {"gain_db": 0, "shareable": False}, + }, + "analog_outputs": {1: {"delay": 0, "shareable": False}}, + } + } + + assert qua_config["elements"] == { + "in_out_channel": { + "operations": {}, + "outputs": {"out1": ("con1", 1), "out2": ("con1", 2)}, + "singleInput": {"port": ("con1", 1)}, + "smearing": 0, + "time_of_flight": 24, + } + } diff --git a/tests/components/channels/test_in_out_IQ_channel.py b/tests/components/channels/test_in_out_IQ_channel.py index 54088de9..da0ddf89 100644 --- a/tests/components/channels/test_in_out_IQ_channel.py +++ b/tests/components/channels/test_in_out_IQ_channel.py @@ -1,5 +1,7 @@ import pytest from quam.components import * +from quam.components.ports.analog_inputs import OPXPlusAnalogInputPort +from quam.components.ports.analog_outputs import OPXPlusAnalogOutputPort def test_empty_in_out_IQ_channel(): @@ -254,3 +256,129 @@ def test_channel_measure(mocker): mocker.patch("quam.components.channels.measure", return_value=1) result = readout_resonator.measure("readout") assert result == (1, 1) + + +def test_empty_in_out_IQ_channel_ports(): + readout_resonator = InOutIQChannel( + frequency_converter_up=FrequencyConverter( + mixer=Mixer(), local_oscillator=LocalOscillator() + ), + opx_output_I=OPXPlusAnalogOutputPort("con1", 1), + opx_output_Q=OPXPlusAnalogOutputPort("con1", 2), + opx_input_I=OPXPlusAnalogInputPort("con1", 3), + opx_input_Q=OPXPlusAnalogInputPort("con1", 4), + intermediate_frequency=100e6, + ) + + assert isinstance(readout_resonator.frequency_converter_up.mixer, Mixer) + + assert isinstance(readout_resonator.local_oscillator, LocalOscillator) + assert ( + readout_resonator.frequency_converter_up.local_oscillator + is readout_resonator.local_oscillator + ) + + mixer = readout_resonator.frequency_converter_up.mixer + assert mixer.intermediate_frequency == 100e6 + + assert mixer.local_oscillator_frequency is None + readout_resonator.local_oscillator.frequency = 5e9 + assert mixer.local_oscillator_frequency == 5e9 + + with pytest.raises(AttributeError): + mixer.name + assert readout_resonator.id is None + with pytest.raises(AttributeError): + readout_resonator.name + + readout_resonator.id = 1 + + d = readout_resonator.to_dict() + assert d == { + "frequency_converter_up": { + "__class__": "quam.components.hardware.FrequencyConverter", + "mixer": {}, + "local_oscillator": {"frequency": 5000000000.0}, + }, + "opx_output_I": { + "controller_id": "con1", + "port_id": 1, + "__class__": "quam.components.ports.analog_outputs.OPXPlusAnalogOutputPort", + }, + "opx_output_Q": { + "controller_id": "con1", + "port_id": 2, + "__class__": "quam.components.ports.analog_outputs.OPXPlusAnalogOutputPort", + }, + "opx_input_I": { + "controller_id": "con1", + "port_id": 3, + "__class__": "quam.components.ports.analog_inputs.OPXPlusAnalogInputPort", + }, + "opx_input_Q": { + "controller_id": "con1", + "port_id": 4, + "__class__": "quam.components.ports.analog_inputs.OPXPlusAnalogInputPort", + }, + "intermediate_frequency": 100000000.0, + "id": 1, + } + + bare_cfg = { + "controllers": {}, + "elements": {}, + "pulses": {}, + "waveforms": {}, + "integration_weights": {}, + } + cfg = bare_cfg.copy() + expected_cfg = { + "controllers": { + "con1": { + "analog_inputs": { + 3: {"gain_db": 0, "shareable": False}, + 4: {"gain_db": 0, "shareable": False}, + }, + "analog_outputs": { + 1: {"delay": 0, "shareable": False}, + 2: {"delay": 0, "shareable": False}, + }, + } + }, + "elements": { + "IQ1": { + "intermediate_frequency": 100000000.0, + "mixInputs": { + "I": ("con1", 1), + "Q": ("con1", 2), + "lo_frequency": 5000000000.0, + "mixer": "IQ1.mixer", + }, + "operations": {}, + "outputs": {"out1": ("con1", 3), "out2": ("con1", 4)}, + "smearing": 0, + "time_of_flight": 24, + } + }, + "pulses": {}, + "waveforms": {}, + "integration_weights": {}, + } + readout_resonator.apply_to_config(cfg) + readout_resonator.opx_output_I.apply_to_config(cfg) + readout_resonator.opx_output_Q.apply_to_config(cfg) + readout_resonator.opx_input_I.apply_to_config(cfg) + readout_resonator.opx_input_Q.apply_to_config(cfg) + assert cfg == expected_cfg + + cfg = bare_cfg.copy() + readout_resonator._default_label = "res" + readout_resonator.apply_to_config(cfg) + expected_cfg["elements"]["res1"] = expected_cfg["elements"].pop("IQ1") + expected_cfg["elements"]["res1"]["mixInputs"]["mixer"] = "IQ1.mixer" + + cfg = bare_cfg.copy() + readout_resonator.id = "resonator_1" + readout_resonator.apply_to_config(cfg) + expected_cfg["elements"]["resonator_1"] = expected_cfg["elements"].pop("res1") + expected_cfg["elements"]["resonator_1"]["mixInputs"]["mixer"] = "resonator_1.mixer" diff --git a/tests/components/channels/test_in_out_single_channel.py b/tests/components/channels/test_in_out_single_channel.py index 5b88d730..ec106727 100644 --- a/tests/components/channels/test_in_out_single_channel.py +++ b/tests/components/channels/test_in_out_single_channel.py @@ -1,5 +1,7 @@ import pytest from quam.components import * +from quam.components.ports.analog_inputs import OPXPlusAnalogInputPort +from quam.components.ports.analog_outputs import OPXPlusAnalogOutputPort def test_in_out_single_channel_empty_error(): @@ -46,3 +48,37 @@ def test_in_out_single_channel(): } assert cfg == expected_cfg + + +def test_in_out_single_channel_ports(): + channel = InOutSingleChannel( + id=1, + opx_output=OPXPlusAnalogOutputPort("con1", 1), + opx_input=OPXPlusAnalogInputPort("con1", 2), + ) + + cfg = {"controllers": {}, "elements": {}} + + channel.apply_to_config(cfg) + channel.opx_output.apply_to_config(cfg) + channel.opx_input.apply_to_config(cfg) + + expected_cfg = { + "controllers": { + "con1": { + "analog_inputs": {2: {"gain_db": 0, "shareable": False}}, + "analog_outputs": {1: {"delay": 0, "shareable": False}}, + } + }, + "elements": { + "ch1": { + "operations": {}, + "outputs": {"out1": ("con1", 2)}, + "singleInput": {"port": ("con1", 1)}, + "smearing": 0, + "time_of_flight": 24, + } + }, + } + + assert cfg == expected_cfg diff --git a/tests/components/channels/test_in_single_channel.py b/tests/components/channels/test_in_single_channel.py index c0c3854e..ff2be564 100644 --- a/tests/components/channels/test_in_single_channel.py +++ b/tests/components/channels/test_in_single_channel.py @@ -1,4 +1,5 @@ from quam.components.channels import InSingleChannel +from quam.components.ports.analog_inputs import OPXPlusAnalogInputPort from quam.utils.dataclass import get_dataclass_attr_annotations @@ -36,3 +37,29 @@ def test_generate_config(qua_config): "time_of_flight": 24, } } + + +def test_generate_config_ports(qua_config): + channel = InSingleChannel( + id="input_channel", opx_input=OPXPlusAnalogInputPort("con1", 1) + ) + + channel.apply_to_config(qua_config) + channel.opx_input.apply_to_config(qua_config) + + assert qua_config["controllers"] == { + "con1": { + "analog_inputs": { + 1: {"gain_db": 0, "shareable": False}, + }, + } + } + + assert qua_config["elements"] == { + "input_channel": { + "operations": {}, + "outputs": {"out1": ("con1", 1)}, + "smearing": 0, + "time_of_flight": 24, + } + } diff --git a/tests/components/channels/test_in_single_out_IQ_channel.py b/tests/components/channels/test_in_single_out_IQ_channel.py index 13df4390..a0b92211 100644 --- a/tests/components/channels/test_in_single_out_IQ_channel.py +++ b/tests/components/channels/test_in_single_out_IQ_channel.py @@ -1,4 +1,6 @@ from quam.components.channels import InSingleOutIQChannel +from quam.components.ports.analog_inputs import OPXPlusAnalogInputPort +from quam.components.ports.analog_outputs import OPXPlusAnalogOutputPort from quam.utils.dataclass import get_dataclass_attr_annotations @@ -61,3 +63,44 @@ def test_generate_config(qua_config): "time_of_flight": 24, } } + + +def test_generate_config(qua_config): + channel = InSingleOutIQChannel( + id="in_out_channel", + opx_input=OPXPlusAnalogInputPort("con1", 1), + opx_output_I=OPXPlusAnalogOutputPort("con1", 1), + opx_output_Q=OPXPlusAnalogOutputPort("con1", 2), + frequency_converter_up=None, + ) + + channel.apply_to_config(qua_config) + channel.opx_input.apply_to_config(qua_config) + channel.opx_output_I.apply_to_config(qua_config) + channel.opx_output_Q.apply_to_config(qua_config) + + assert qua_config["controllers"] == { + "con1": { + "analog_inputs": { + 1: {"gain_db": 0, "shareable": False}, + }, + "analog_outputs": { + 1: {"delay": 0, "shareable": False}, + 2: {"delay": 0, "shareable": False}, + }, + } + } + + assert qua_config["elements"] == { + "in_out_channel": { + "intermediate_frequency": 0.0, + "mixInputs": { + "I": ("con1", 1), + "Q": ("con1", 2), + }, + "operations": {}, + "outputs": {"out1": ("con1", 1)}, + "smearing": 0, + "time_of_flight": 24, + } + } diff --git a/tests/components/channels/test_single_channel.py b/tests/components/channels/test_single_channel.py index f3caccf7..12564339 100644 --- a/tests/components/channels/test_single_channel.py +++ b/tests/components/channels/test_single_channel.py @@ -3,6 +3,7 @@ from copy import deepcopy from quam.components import * +from quam.components.ports.analog_outputs import OPXPlusAnalogOutputPort from quam.core import quam_dataclass, QuamRoot @@ -147,3 +148,46 @@ def test_instantiate_single_channel(): d_loaded = json.loads(d_json) instantiate_quam_class(SingleChannel, d_loaded) + + +def test_generate_config(qua_config): + channel = SingleChannel( + id="out_channel", + opx_output=("con1", 1), + ) + + channel.apply_to_config(qua_config) + + assert qua_config["controllers"] == { + "con1": { + "analog_outputs": { + 1: {"delay": 0, "shareable": False}, + }, + } + } + + assert qua_config["elements"] == { + "out_channel": {"operations": {}, "singleInput": {"port": ("con1", 1)}} + } + + +def test_generate_config_port(qua_config): + channel = SingleChannel( + id="out_channel", + opx_output=OPXPlusAnalogOutputPort("con1", 1), + ) + + channel.apply_to_config(qua_config) + channel.opx_output.apply_to_config(qua_config) + + assert qua_config["controllers"] == { + "con1": { + "analog_outputs": { + 1: {"delay": 0, "shareable": False}, + }, + } + } + + assert qua_config["elements"] == { + "out_channel": {"operations": {}, "singleInput": {"port": ("con1", 1)}} + }