Skip to content

Commit

Permalink
add tests for qubit
Browse files Browse the repository at this point in the history
  • Loading branch information
nulinspiratie committed Dec 2, 2024
1 parent d2d6301 commit afe6c6c
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 127 deletions.
15 changes: 14 additions & 1 deletion quam/components/quantum_components/qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from quam.components.pulses import Pulse
from quam.components.quantum_components import QuantumComponent
from quam.core import quam_dataclass
from quam.utils import string_reference as str_ref

if TYPE_CHECKING:
from quam.components.macro import QubitMacro
Expand All @@ -22,9 +23,21 @@

@quam_dataclass
class Qubit(QuantumComponent):
id: Union[str, int]
id: Union[str, int] = "#./inferred_id"
macros: Dict[str, MacroType] = field(default_factory=dict)

@property
def inferred_id(self) -> Union[str, int]:
if not str_ref.is_reference(self.get_unreferenced_value("id")):
return self.id
elif self.parent is not None:
name = self.parent.get_attr_name(self)
return name
else:
raise AttributeError(
f"Cannot infer id of {self} because it is not attached to a parent"
)

@property
def name(self) -> str:
"""Returns the name of the qubit"""
Expand Down
9 changes: 9 additions & 0 deletions quam/components/quantum_components/qubit_pair.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from quam.core import quam_dataclass
from quam.components.quantum_components import QuantumComponent, Qubit
from quam.utils import string_reference as str_ref

if TYPE_CHECKING:
from quam.components.macro import QubitPairMacro
Expand All @@ -14,10 +15,18 @@

@quam_dataclass
class QubitPair(QuantumComponent):
id: str = "#./name"
qubit_control: Qubit
qubit_target: Qubit
macros: Dict[str, MacroType] = field(default_factory=dict)

@property
def name(self) -> str:
if not str_ref.is_reference(self.get_unreferenced_value("id")):
return self.id
else:
return f"{self.qubit_control.name}@{self.qubit_target.name}"

def align(self):
"""Aligns the execution of all channels of both qubits"""
self.qubit_control.align(self.qubit_target)
62 changes: 62 additions & 0 deletions tests/components/quantum_components/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from typing import Dict, Optional
import pytest
from quam.components import Qubit, QubitPair
from quam.components.channels import IQChannel
from quam.core.quam_classes import QuamRoot, quam_dataclass
from dataclasses import field


@quam_dataclass
class MockQubit(Qubit):
xy: IQChannel
resonator: Optional[IQChannel] = None


@quam_dataclass
class TestQUAM(QuamRoot):
qubits: Dict[str, MockQubit] = field(default_factory=dict)
qubit_pairs: Dict[str, QubitPair] = field(default_factory=dict)


@pytest.fixture
def mock_qubit():
"""Basic mock qubit with xy channel"""
return MockQubit(
id="q0",
xy=IQChannel(
opx_output_I=("con1", 1),
opx_output_Q=("con1", 2),
frequency_converter_up=None,
),
)


@pytest.fixture
def mock_qubit_with_resonator():
"""Mock qubit with both xy and resonator channels"""
return MockQubit(
id="q1",
xy=IQChannel(
opx_output_I=("con1", 1),
opx_output_Q=("con1", 2),
frequency_converter_up=None,
),
resonator=IQChannel(
opx_output_I=("con1", 3),
opx_output_Q=("con1", 4),
frequency_converter_up=None,
),
)


@pytest.fixture
def test_quam(mock_qubit, mock_qubit_with_resonator):
"""Test QUAM instance with qubits and qubit pairs"""
machine = TestQUAM(
qubits={"q0": mock_qubit, "q1": mock_qubit_with_resonator},
)
machine.qubit_pairs["pair_0"] = QubitPair(
qubit_control=mock_qubit.get_reference(),
qubit_target=mock_qubit_with_resonator.get_reference(),
)
return machine
101 changes: 101 additions & 0 deletions tests/components/quantum_components/test_qubit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from typing import Optional
import pytest
from quam.components import Qubit
from quam.components.channels import IQChannel
from quam.components.pulses import SquarePulse
from quam.core.quam_classes import QuamRoot, quam_dataclass


def test_qubit_name_int():
qubit = Qubit(id=0)
assert qubit.name == "q0"


def test_qubit_name_str():
qubit = Qubit(id="qubit0")
assert qubit.name == "qubit0"


def test_qubit_channels(mock_qubit_with_resonator):
assert mock_qubit_with_resonator.channels == {
"xy": mock_qubit_with_resonator.xy,
"resonator": mock_qubit_with_resonator.resonator,
}


def test_qubit_channels_referenced(mock_qubit):
# Set resonator as a reference to xy channel
mock_qubit.resonator = "#./xy"

assert mock_qubit.channels == {
"xy": mock_qubit.xy,
"resonator": mock_qubit.xy,
}


def test_qubit_get_pulse_not_found(mock_qubit):
with pytest.raises(ValueError, match="Pulse test_pulse not found"):
mock_qubit.get_pulse("test_pulse")


def test_qubit_get_pulse_not_unique(mock_qubit_with_resonator):
mock_qubit_with_resonator.xy.operations["test_pulse"] = SquarePulse(
length=100, amplitude=1.0
)
mock_qubit_with_resonator.resonator.operations["test_pulse"] = SquarePulse(
length=100, amplitude=1.0
)

with pytest.raises(ValueError, match="Pulse test_pulse is not unique"):
mock_qubit_with_resonator.get_pulse("test_pulse")


def test_qubit_get_pulse_unique(mock_qubit):
pulse = SquarePulse(length=100, amplitude=1.0)
mock_qubit.xy.operations["test_pulse"] = pulse

assert mock_qubit.get_pulse("test_pulse") == pulse


def test_qubit_align(mock_qubit_with_resonator, mocker):
mocker.patch("quam.components.quantum_components.qubit.align")
mock_qubit_with_resonator.align(mock_qubit_with_resonator)

from quam.components.quantum_components.qubit import align

align.assert_called_once_with("q1.xy", "q1.resonator")


def test_qubit_get_macros(mock_qubit):
assert mock_qubit.macros == {}
assert mock_qubit.get_macros() == {"align": mock_qubit.align}


def test_qubit_apply_align(mock_qubit_with_resonator, mocker):
mocker.patch("quam.components.quantum_components.qubit.align")
mock_qubit_with_resonator.apply("align")

from quam.components.quantum_components.qubit import align

align.assert_called_once_with("q1.xy", "q1.resonator")


def test_qubit_inferred_id_direct():
"""Test inferred_id when id is a direct value"""
qubit = Qubit(id=0)
assert qubit.inferred_id == 0


def test_qubit_inferred_id_with_parent(test_quam):
"""Test inferred_id when id is a reference and qubit has parent"""
test_quam.qubits["q2"] = Qubit()
assert test_quam.qubits["q2"].inferred_id == "q2"


def test_qubit_inferred_id_no_parent():
"""Test inferred_id when id is a reference but qubit has no parent"""
qubit = Qubit(id="#./inferred_id")
with pytest.raises(
AttributeError, match="Cannot infer id .* not attached to a parent"
):
_ = qubit.inferred_id
116 changes: 116 additions & 0 deletions tests/components/quantum_components/test_qubit_pair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from typing import Dict, List
import pytest
from quam.components import Qubit, QubitPair
from quam.components.channels import IQChannel
from quam.core.quam_classes import QuamRoot, quam_dataclass


@quam_dataclass
class MockQubit(Qubit):
xy: IQChannel = None


@quam_dataclass
class MockQubitPair(QubitPair):
qubit_control: MockQubit
qubit_target: MockQubit


@quam_dataclass
class QUAM(QuamRoot):
qubits: Dict[str, MockQubit]
qubit_pairs: str[str, MockQubitPair]


@pytest.fixture
def test_qubit_control():
return MockQubit(
id="q1",
xy=IQChannel(
id="xy_control",
opx_output_I=("con1", 1),
opx_output_Q=("con1", 2),
frequency_converter_up=None,
),
)


@pytest.fixture
def test_qubit_target():
return MockQubit(
id="q2",
xy=IQChannel(
id="xy_target",
opx_output_I=("con1", 5),
opx_output_Q=("con1", 6),
frequency_converter_up=None,
),
)


@pytest.fixture
def test_qubit_pair(test_qubit_control, test_qubit_target):
return MockQubitPair(
id="pair_1",
qubit_control=test_qubit_control,
qubit_target=test_qubit_target
)


@pytest.fixture
def test_quam(test_qubit_control, test_qubit_target, test_qubit_pair):
return QUAM(
qubits={"control": test_qubit_control, "target": test_qubit_target},
qubit_pairs=[test_qubit_pair],
)


def test_qubit_pair_initialization(test_qubit_pair, test_qubit_control, test_qubit_target):
"""Test that QubitPair is initialized correctly"""
assert test_qubit_pair.qubit_control == test_qubit_control
assert test_qubit_pair.qubit_target == test_qubit_target
assert test_qubit_pair.name == "pair_1"
assert isinstance(test_qubit_pair.macros, dict)
assert len(test_qubit_pair.macros) == 0


def test_qubit_pair_align(test_qubit_pair, mocker):
"""Test that align method calls the control qubit's align method with correct args"""
mock_align = mocker.patch.object(test_qubit_pair.qubit_control, 'align')

test_qubit_pair.align()

mock_align.assert_called_once_with(test_qubit_pair.qubit_target)


def test_qubit_pair_via_matmul(test_quam):
"""Test that qubit pair can be accessed via @ operator"""
control = test_quam.qubits["control"]
target = test_quam.qubits["target"]

qubit_pair = control @ target

assert isinstance(qubit_pair, QubitPair)
assert qubit_pair.qubit_control == control
assert qubit_pair.qubit_target == target


def test_matmul_with_invalid_qubit(test_quam):
"""Test that @ operator raises error for invalid qubit pairs"""
control = test_quam.qubits["control"]

with pytest.raises(ValueError, match="Cannot create a qubit pair with same qubit"):
_ = control @ control

with pytest.raises(ValueError, match="Cannot create a qubit pair .* with a non-qubit object"):
_ = control @ "not_a_qubit"


def test_matmul_with_nonexistent_pair(test_quam):
"""Test that @ operator raises error for non-existent qubit pairs"""
target = test_quam.qubits["target"]
control = test_quam.qubits["control"]

# Try to access pair in reverse order (target @ control) when only (control @ target) exists
with pytest.raises(ValueError, match="Qubit pair not found"):
_ = target @ control
Loading

0 comments on commit afe6c6c

Please sign in to comment.