Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Added

- Add Single Gate Multi Qubit (SGMQ) notation to the `CircuitBuilder`
- The cycle time [seconds] can be set when instantiating the `QuantifySchedulerExporter` through the `cycle_time`
parameter
- `CircuitBuilder` accepts multiple (qu)bit registers through `add_register` method
Expand Down
64 changes: 56 additions & 8 deletions opensquirrel/circuit_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
from typing_extensions import Self

from opensquirrel.circuit import Circuit
from opensquirrel.default_instructions import default_instruction_set
from opensquirrel.default_instructions import default_instruction_set, default_two_qubit_gate_set
from opensquirrel.ir import IR, AsmDeclaration, Bit, BitLike, Instruction, Qubit, QubitLike
from opensquirrel.register_manager import (
DEFAULT_BIT_REGISTER_NAME,
DEFAULT_QUBIT_REGISTER_NAME,
BitRegister,
QubitRegister,
Register,
RegisterManager,
)

Expand Down Expand Up @@ -118,6 +119,14 @@ def _check_out_of_bounds_access(self, instruction: Instruction) -> None:
for bit in instruction.bit_operands:
self._check_bit_out_of_bounds_access(bit)

@staticmethod
def _expand_sgmq_arg(arg: Any) -> list[Any]:
if isinstance(arg, Register):
return list(arg)
if isinstance(arg, list):
return arg
return [arg]

def _add_statement(self, attr: str, *args: Any) -> Self:
if attr == "asm":
try:
Expand All @@ -131,17 +140,56 @@ def _add_statement(self, attr: str, *args: Any) -> Self:
if attr not in default_instruction_set:
msg = f"unknown instruction {attr!r}"
raise ValueError(msg)
try:
instruction = default_instruction_set[attr](*args)
except TypeError as e:
msg = f"trying to build {attr!r} with the wrong number or type of arguments: {args!r}: {e}"
raise TypeError(msg) from e

self._check_out_of_bounds_access(instruction)
sgmq_args = self._expand_sgmq_args(attr, args)

for expanded_args in sgmq_args:
try:
instruction = default_instruction_set[attr](*expanded_args)
except TypeError as e:
msg = f"trying to build {attr!r} with the wrong number or type of arguments: {expanded_args!r}: {e}"
raise TypeError(msg) from e

self._check_out_of_bounds_access(instruction)
self.ir.add_statement(instruction)

self.ir.add_statement(instruction)
return self

def _expand_sgmq_args(self, attr: str, args: tuple[Any, ...]) -> list[tuple[Any, ...]]:
if not isinstance(args[0], (Register, list)):
return [args]

is_two_operand_instruction = attr in default_two_qubit_gate_set or attr == "measure"

if len(args) > 1 and is_two_operand_instruction:
second_expandable = isinstance(args[1], (Register, list))

if second_expandable:
expanded_first = self._expand_sgmq_arg(args[0])
expanded_second = self._expand_sgmq_arg(args[1])

if len(expanded_first) != len(expanded_second):
msg = (
f"SGMQ requires matching operand lengths: got {len(expanded_first)} and {len(expanded_second)}"
)
raise ValueError(msg)

remaining_args = args[2:]
return [
(first, second, *remaining_args)
for first, second in zip(expanded_first, expanded_second, strict=True)
]

msg = (
"SGMQ notation for multi-operand instructions requires operands to be "
"lists or registers (and of equal length)"
)
raise ValueError(msg)

expanded_first = self._expand_sgmq_arg(args[0])
remaining_args = args[1:]
return [(first, *remaining_args) for first in expanded_first]

def to_circuit(self) -> Circuit:
"""Build the circuit.

Expand Down
181 changes: 181 additions & 0 deletions tests/test_circuit_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,184 @@ def test_register_negative_index(self) -> None:

assert q0[-1] == q0_size + q0_neg_index
assert q1[-2] == q0_size + q1_size + q1_neg_index


class TestSGMQNotation:
def test_sgmq_list(self) -> None:
builder = CircuitBuilder(3)
builder.H([0, 1, 2])
circuit = builder.to_circuit()

assert circuit.ir.statements == [H(0), H(1), H(2)]

def test_sgmq_register(self) -> None:
builder = CircuitBuilder()
q0 = QubitRegister(3, "q0")
builder.add_register(q0)
builder.H(q0)
circuit = builder.to_circuit()

assert circuit.ir.statements == [H(0), H(1), H(2)]

def test_sgmq_multiple_registers(self) -> None:
builder = CircuitBuilder()
q0 = QubitRegister(3, "q0")
q1 = QubitRegister(2, "q1")
builder.add_register(q0)
builder.add_register(q1)

builder.H(q0)
builder.H(q1)

circuit = builder.to_circuit()

assert circuit.ir.statements == [H(0), H(1), H(2), H(3), H(4)]

def test_sgmq_parametric_gate(self) -> None:
builder = CircuitBuilder(3)
builder.Rx([0, 1, 2], math.pi / 2)
builder.U([0, 1], 1.0, 2.0, 3.0)
circuit = builder.to_circuit()

assert circuit.ir.statements == [
Rx(0, math.pi / 2),
Rx(1, math.pi / 2),
Rx(2, math.pi / 2),
U(0, 1.0, 2.0, 3.0),
U(1, 1.0, 2.0, 3.0),
]

def test_sgmq_parametric_register(self) -> None:
builder = CircuitBuilder()
q0 = QubitRegister(2, "q0")
builder.add_register(q0)
builder.Ry(q0, 1.5)
circuit = builder.to_circuit()

assert circuit.ir.statements == [Ry(0, 1.5), Ry(1, 1.5)]

def test_sgmq_measure(self) -> None:
builder = CircuitBuilder(3, 2)
builder.measure([1, 2], [0, 1])
circuit = builder.to_circuit()

assert circuit.ir.statements == [Measure(1, 0), Measure(2, 1)]

def test_sgmq_measure_registers(self) -> None:
builder = CircuitBuilder()
q0 = QubitRegister(2, "q0")
b0 = BitRegister(2, "b0")
builder.add_register(q0)
builder.add_register(b0)

builder.measure(q0, b0)
circuit = builder.to_circuit()

assert circuit.ir.statements == [Measure(0, 0), Measure(1, 1)]

def test_sgmq_measure_slice(self) -> None:
builder = CircuitBuilder()
q0 = QubitRegister(3, "q0")
b0 = BitRegister(4, "b0")
builder.add_register(q0)
builder.add_register(b0)

builder.measure(q0[0:2], b0[1:3])
circuit = builder.to_circuit()

assert circuit.ir.statements == [Measure(0, 1), Measure(1, 2)]

def test_sgmq_measure_error(self) -> None:
builder = CircuitBuilder(3, 2)

with pytest.raises(ValueError, match="SGMQ requires matching operand lengths: got 3 and 2"):
builder.measure([0, 1, 2], [0, 1])

def test_sgmq_measure_register_error(self) -> None:
builder = CircuitBuilder()
q0 = QubitRegister(3, "q0")
b0 = BitRegister(2, "b0")
builder.add_register(q0)
builder.add_register(b0)

with pytest.raises(ValueError, match="SGMQ requires matching operand lengths: got 3 and 2"):
builder.measure(q0, b0)

def test_sgmq_init_reset(self) -> None:
builder = CircuitBuilder(3)
builder.init([0, 1, 2])
builder.reset([0, 1, 2])
circuit = builder.to_circuit()

assert circuit.ir.statements == [Init(0), Init(1), Init(2), Reset(0), Reset(1), Reset(2)]

def test_sgmq_barrier_wait(self) -> None:
builder = CircuitBuilder(3)
builder.barrier([0, 1, 2])
builder.wait([0, 1], 10)
circuit = builder.to_circuit()

assert circuit.ir.statements == [Barrier(0), Barrier(1), Barrier(2), Wait(0, 10), Wait(1, 10)]

def test_sgmq_mixed_with_regular_calls(self) -> None:
builder = CircuitBuilder(4)
builder.H([0, 1])
builder.X(2)
builder.Y([3])
circuit = builder.to_circuit()

assert circuit.ir.statements == [H(0), H(1), X(2), Y(3)]

def test_sgmq_empty_list(self) -> None:
builder = CircuitBuilder(3)
builder.H([])
circuit = builder.to_circuit()

assert circuit.ir.statements == []

def test_sgmq_chaining(self) -> None:
circuit = CircuitBuilder(3).H([0, 1]).X([2]).to_circuit()

assert circuit.ir.statements == [H(0), H(1), X(2)]

def test_sgmq_out_of_bounds_error(self) -> None:
builder = CircuitBuilder(3)

with pytest.raises(IndexError, match="qubit index 10 is out of bounds"):
builder.H([0, 1, 10])

def test_sgmq_two_qubit_gates(self) -> None:
builder = CircuitBuilder(4)
builder.CNOT([0, 1], [2, 3])
builder.SWAP([0, 1], [2, 3])
builder.CZ([0, 1], [2, 3])
circuit = builder.to_circuit()

assert circuit.ir.statements == [CNOT(0, 2), CNOT(1, 3), SWAP(0, 2), SWAP(1, 3), CZ(0, 2), CZ(1, 3)]

def test_sgmq_two_qubit_error(self) -> None:
builder = CircuitBuilder(4)

with pytest.raises(
ValueError,
match=(
"SGMQ notation for multi-operand instructions requires operands to be "
"lists or registers \\(and of equal length\\)"
),
):
builder.CNOT([0, 1, 2], 3)
Comment thread
elenbaasc marked this conversation as resolved.

def test_sgmq_two_qubit_different_operand_types(self) -> None:
builder = CircuitBuilder(2)
q0 = QubitRegister(2, "q0")
builder.add_register(q0)
builder.CNOT([0, 1], q0)
circuit = builder.to_circuit()

assert circuit.ir.statements == [CNOT(0, 2), CNOT(1, 3)]

def test_sgmq_length_mismatch(self) -> None:
builder = CircuitBuilder(4)

with pytest.raises(ValueError, match="SGMQ requires matching operand lengths: got 3 and 2"):
builder.CNOT([0, 1, 2], [1, 2])
Loading
Loading