Skip to content

Commit

Permalink
Merge commit 'da08671ddc3aa11773c0fc6cc2c107011019a3b7' into feat/mea…
Browse files Browse the repository at this point in the history
…sure-channels
  • Loading branch information
nulinspiratie committed Apr 12, 2024
2 parents eb93202 + da08671 commit 8950e53
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ additional
.idea
build
site
calibration_db.json

dependency_links.txt
PKG-INFO
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
- Add optional `config_settings` property to quam components indicating that they should be called before/after other components when generating QUA configuration
- Added `InOutIQChannel.measure_accumulated/sliced`
- Added `ReadoutPulse`. All readout pulses can now be created simply by inheriting from the `ReadoutPulse` and the non-readout variant.
- Added `Channel.set_dc_offset`

### Changed
- Pulses with `pulse.axis_angle = None` are now compatible with an `IQChannel` as all signal on the I port.

### Fixed
- Switched channel `RF_inputs` and `RF_outputs` for Octave
- Loading QuAM components when the expected type is a union or the actual type is a list
no longer raises an error
- The qua config entries from OctaveUpConverter entries I/Q_connection were of type
QuamList, resulting in errors during deepcopy. Converted to tuple

## [0.3.0]
### Added
Expand Down
30 changes: 28 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,2 +1,28 @@
Copyright (c) 2023 Quantum Machines
All rights reserved.
BSD 3-Clause License

Copyright (c) 2024, Quantum Machines

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 changes: 34 additions & 1 deletion quam/components/channels.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import field
from typing import ClassVar, Dict, List, Optional, Sequence, Tuple, Union
from typing import ClassVar, Dict, List, Optional, Sequence, Literal, Tuple, Union
import warnings

from quam.components.hardware import BaseFrequencyConverter, Mixer, LocalOscillator
Expand All @@ -16,6 +16,7 @@
wait,
measure,
declare,
set_dc_offset,
fixed,
demod,
dual_demod,
Expand Down Expand Up @@ -411,6 +412,18 @@ class SingleChannel(Channel):
opx_output_offset: float = None
intermediate_frequency: float = None

def set_dc_offset(self, offset: QuaNumberType):
"""Set the DC offset of an element's input to the given value.
This value will remain the DC offset until changed or until the Quantum Machine
is closed.
Args:
offset (QuaNumberType): The DC offset to set the input to.
This is limited by the OPX output voltage range.
The number can be a QUA variable
"""
set_dc_offset(element=self.name, element_input="single", offset=offset)

def apply_to_config(self, config: dict):
"""Adds this SingleChannel to the QUA configuration.
Expand Down Expand Up @@ -780,6 +793,26 @@ def mixer(self) -> Optional[Mixer]:
def rf_frequency(self):
return self.frequency_converter_up.LO_frequency + self.intermediate_frequency

def set_dc_offset(self, offset: QuaNumberType, element_input: Literal["I", "Q"]):
"""Set the DC offset of an element's input to the given value.
This value will remain the DC offset until changed or until the Quantum Machine
is closed.
Args:
offset (QuaNumberType): The DC offset to set the input to.
This is limited by the OPX output voltage range.
The number can be a QUA variable
element_input (Literal["I", "Q"]): The element input to set the offset for.
Raises:
ValueError: If element_input is not "I" or "Q"
"""
if element_input not in ["I", "Q"]:
raise ValueError(
f"element_input should be either 'I' or 'Q', got {element_input}"
)
set_dc_offset(element=self.name, element_input=element_input, offset=offset)

def apply_to_config(self, config: dict):
"""Adds this IQChannel to the QUA configuration.
Expand Down
4 changes: 2 additions & 2 deletions quam/components/octave.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,8 @@ def apply_to_config(self, config: Dict) -> None:
if isinstance(self.channel, SingleChannel):
output_config["I_connection"] = self.channel.opx_output
elif isinstance(self.channel, IQChannel):
output_config["I_connection"] = self.channel.opx_output_I
output_config["Q_connection"] = self.channel.opx_output_Q
output_config["I_connection"] = tuple(self.channel.opx_output_I)
output_config["Q_connection"] = tuple(self.channel.opx_output_Q)


@quam_dataclass
Expand Down
3 changes: 2 additions & 1 deletion quam/core/quam_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,8 @@ def to_dict(
follow_references=follow_references,
include_defaults=include_defaults,
)
if not self._val_matches_attr_annotation(attr, val):
val_is_list = isinstance(val, (list, UserList))
if not self._val_matches_attr_annotation(attr, val) and not val_is_list:
quam_dict[attr]["__class__"] = get_full_class_path(val)
else:
quam_dict[attr] = val
Expand Down
39 changes: 26 additions & 13 deletions quam/core/quam_instantiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,15 @@ def instantiate_attrs_from_list(

instantiated_attr_list = []
for k, attr_val in enumerate(attr_list):
if not required_subtype:
if isinstance(attr_val, dict) and "__class__" in attr_val:
instantiated_attr = instantiate_quam_class(
get_class_from_path(attr_val["__class__"]),
attr_val,
fix_attrs=fix_attrs,
validate_type=validate_type,
str_repr=f"{str_repr}[{k}]",
)
elif not required_subtype:
instantiated_attr_list.append(attr_val)
continue
elif typing.get_origin(required_subtype) == list:
Expand Down Expand Up @@ -136,8 +144,8 @@ def instantiate_attrs_from_list(
else:
instantiated_attr = attr_val
# Add custom __class__ QuamComponent logic here

validate_obj_type(instantiated_attr, required_subtype, str_repr=str_repr)
if required_subtype:
validate_obj_type(instantiated_attr, required_subtype, str_repr=str_repr)

instantiated_attr_list.append(instantiated_attr)

Expand Down Expand Up @@ -195,6 +203,14 @@ def instantiate_attr(
validate_type=validate_type,
str_repr=str_repr,
)
elif isinstance(attr_val, dict) and "__class__" in attr_val:
instantiated_attr = instantiate_quam_class(
quam_class=expected_type,
contents=attr_val,
fix_attrs=fix_attrs,
validate_type=validate_type,
str_repr=str_repr,
)
elif isinstance(expected_type, dict) or typing.get_origin(expected_type) == dict:
instantiated_attr = instantiate_attrs_from_dict(
attr_dict=attr_val,
Expand All @@ -205,7 +221,11 @@ def instantiate_attr(
)
if typing.get_origin(expected_type) == dict:
expected_type = dict
elif isinstance(expected_type, list) or typing.get_origin(expected_type) == list:
elif (
isinstance(expected_type, list)
or typing.get_origin(expected_type) == list
or isinstance(attr_val, list)
):
instantiated_attr = instantiate_attrs_from_list(
attr_list=attr_val,
required_type=expected_type,
Expand All @@ -215,17 +235,10 @@ def instantiate_attr(
)
if typing.get_origin(expected_type) == list:
expected_type = list
elif typing.get_origin(expected_type) == tuple:
instantiated_attr = tuple(instantiated_attr)
elif typing.get_origin(expected_type) == typing.Union:
if not all(
t in [str, int, float, bool, type(None)]
for t in typing.get_args(expected_type)
):
raise TypeError(
"Currently only Union[str, int, float, bool] is supported, whereas "
f"{expected_type} was found in {str_repr}"
)
instantiated_attr = attr_val

elif typing.get_origin(expected_type) == tuple:
if isinstance(attr_val, list):
attr_val = tuple(attr_val)
Expand Down
27 changes: 27 additions & 0 deletions tests/components/channels/test_IQ_channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import pytest
from quam.components import IQChannel


def test_IQ_channel_set_dc_offset(mocker):
mocker.patch("quam.components.channels.set_dc_offset")

channel = IQChannel(
id="channel",
opx_output_I=("con1", 1),
opx_output_Q=("con1", 2),
frequency_converter_up=None,
)

with pytest.raises(TypeError):
channel.set_dc_offset(0.5)

with pytest.raises(ValueError):
channel.set_dc_offset(0.5, "X")

channel.set_dc_offset(0.5, "I")

from quam.components.channels import set_dc_offset

set_dc_offset.assert_called_once_with(
element="channel", element_input="I", offset=0.5
)
13 changes: 13 additions & 0 deletions tests/components/channels/test_single_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,16 @@ class QuamTest(QuamRoot):
cfg = machine.generate_config()
expected_cfg["controllers"]["con1"]["analog_outputs"][1]["offset"] = 0.1
assert cfg == expected_cfg


def test_single_channel_set_dc_offset(mocker):
mocker.patch("quam.components.channels.set_dc_offset")

channel = SingleChannel(id="channel", opx_output=("con1", 1))
channel.set_dc_offset(0.5)

from quam.components.channels import set_dc_offset

set_dc_offset.assert_called_once_with(
element="channel", element_input="single", offset=0.5
)
6 changes: 3 additions & 3 deletions tests/components/test_octave.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ def test_channel_add_RF_outputs(octave):
expected_cfg_elements = {
"ch": {
"intermediate_frequency": 0.0,
"RF_outputs": {"port": ("octave1", 2)},
"RF_inputs": {"port": ("octave1", 2)},
"operations": {},
}
}
Expand Down Expand Up @@ -313,8 +313,8 @@ def test_channel_add_RF_inputs(octave):
expected_cfg_elements = {
"ch": {
"intermediate_frequency": 0.0,
"RF_outputs": {"port": ("octave1", 3)},
"RF_inputs": {"port": ("octave1", 4)},
"RF_inputs": {"port": ("octave1", 3)},
"RF_outputs": {"port": ("octave1", 4)},
"operations": {},
"smearing": 0,
"time_of_flight": 24,
Expand Down
35 changes: 35 additions & 0 deletions tests/instantiation/test_instantiate_explicit_class.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass
from typing import List, Union
from quam.core import *
from quam.core.quam_instantiation import *
from quam.utils import get_full_class_path
Expand All @@ -25,3 +26,37 @@ def test_instantiate_from_class():
assert type(loaded_explicit_component) == type(loaded_explicit_component2)

assert isinstance(loaded_explicit_component, QuamComponentTest)


def test_instantiate_nondefault_list_from_dict():
@quam_dataclass
class QuamBasicComponent(QuamComponent):
l: Union[int, List[int]] = 42

quam_component = QuamBasicComponent(l=[1, 2, 3])

d = quam_component.to_dict()
instantiate_quam_class(QuamBasicComponent, d)


@quam_dataclass
class QuamBasicComponent(QuamComponent):
int_val: int = 42


@quam_dataclass
class QuamOuterComponent(QuamComponent):
list_basic_components: Union[int, List[QuamBasicComponent]]


def test_instantiate_explicit_class_with_union_type():
quam_component = QuamOuterComponent(
list_basic_components=[QuamBasicComponent(int_val=42)]
)

d = quam_component.to_dict()
component = instantiate_quam_class(QuamOuterComponent, d)

assert isinstance(component, QuamOuterComponent)
assert isinstance(component.list_basic_components[0], QuamBasicComponent)
assert component.to_dict() == quam_component.to_dict()
11 changes: 10 additions & 1 deletion tests/instantiation/test_instantiation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import pytest
from typing import List, Literal, Optional
from typing import List, Literal, Optional, Tuple

from quam.core import QuamRoot, QuamComponent, quam_dataclass
from quam.examples.superconducting_qubits.components import Transmon
Expand Down Expand Up @@ -317,3 +317,12 @@ def test_instance_attr_literal_fail():
attr_val=1,
expected_type=Literal["a", "b", "c"],
)


def test_isntantiate_tuple():
@quam_dataclass
class TestQuamTuple(QuamComponent):
tuple_val: Tuple[int, str]

obj = instantiate_quam_class(TestQuamTuple, {"tuple_val": [42, "hello"]})
assert obj.tuple_val == (42, "hello")
5 changes: 5 additions & 0 deletions tests/quam_base/test_quam_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,8 @@ def test_list_get_attrs_error():
quam_list = QuamList()
with pytest.raises(NotImplementedError):
quam_list.get_attrs()


def test_list_to_dict():
quam_list = QuamList([1, 2, 3])
assert quam_list.to_dict() == [1, 2, 3]
9 changes: 9 additions & 0 deletions tests/quam_base/test_to_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,12 @@ class QuamBasicComponent(QuamComponent):

quam_component = QuamBasicComponent(l=[1, 2, 3])
assert quam_component.to_dict() == {"l": [1, 2, 3]}


def test_list_to_dict_nondefault():
@quam_dataclass
class QuamBasicComponent(QuamComponent):
l: int = 42

quam_component = QuamBasicComponent(l=[1, 2, 3])
assert quam_component.to_dict() == {"l": [1, 2, 3]}

0 comments on commit 8950e53

Please sign in to comment.