From 083ac12e1bb0bebbdac096d00ccf2f18ab0c94e7 Mon Sep 17 00:00:00 2001 From: David Bakker Date: Fri, 23 Oct 2026 09:13:53 +0200 Subject: [PATCH 1/3] first --- opensquirrel/default_instructions.py | 8 +++++ opensquirrel/ir/two_qubit_gate.py | 52 ++++++++++++++++++++++++++-- opensquirrel/writer/writer.py | 3 +- tests/writer/test_writer.py | 7 ++-- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/opensquirrel/default_instructions.py b/opensquirrel/default_instructions.py index abfbba01..4a3625bd 100644 --- a/opensquirrel/default_instructions.py +++ b/opensquirrel/default_instructions.py @@ -86,6 +86,11 @@ **default_bsr_with_param_set, } +default_two_qubit_gate_with_param_set: dict[str, type[TwoQubitGate]] = { + "CRk": CRk, + "CR": CR, +} + default_two_qubit_gate_set: dict[str, type[TwoQubitGate]] = { "CNOT": CNOT, "CR": CR, @@ -116,6 +121,7 @@ "Identity": I, "Toffoli": CCX, } + default_gate_set: dict[str, type[Gate]] = { **default_single_qubit_gate_set, **default_two_qubit_gate_set, @@ -128,10 +134,12 @@ "measure": Measure, "reset": Reset, } + default_control_instruction_set: dict[str, type[ControlInstruction]] = { "barrier": Barrier, "wait": Wait, } + default_instruction_set: dict[str, type[Instruction]] = { **default_gate_set, **default_non_unitary_set, diff --git a/opensquirrel/ir/two_qubit_gate.py b/opensquirrel/ir/two_qubit_gate.py index 156d7402..0237238a 100644 --- a/opensquirrel/ir/two_qubit_gate.py +++ b/opensquirrel/ir/two_qubit_gate.py @@ -1,12 +1,58 @@ +from __future__ import annotations + from functools import cached_property -from typing import Any +from typing import TYPE_CHECKING, Any import numpy as np -from opensquirrel.ir import Gate, IRVisitor, Qubit, QubitLike +from opensquirrel.ir import Float, Gate, IRVisitor, Qubit, QubitLike from opensquirrel.ir.semantics import CanonicalGateSemantic, ControlledGateSemantic, MatrixGateSemantic from opensquirrel.ir.semantics.bsr import bsr_from_matrix -from opensquirrel.ir.semantics.gate_semantic import GateSemantic + +if TYPE_CHECKING: + from opensquirrel.ir.semantics.gate_semantic import GateSemantic + + +def try_match_replace_with_default_two_qubit_gate(gate: TwoQubitGate) -> TwoQubitGate: + """Tries to match a given two-qubit gate with a _default_ two-qubit gate. + It does that by checking if the parameters of the Bloch sphere rotation (BSR) semantic of the + input two-qubit gate are close to those of any of the default two-qubit gates. + + Note: + - The default (two-qubit) gates are defined in the + [cQASM standard gate set](https://qutech-delft.github.io/cQASM-spec/latest/standard_gate_set/index.html). + - If no specific match is found, the general Rn gate is returned with the same parameters + values as those of the input gate. + + Args: + gate: The two-qubit gate to be matched. + + Returns: + A default two-qubit gate if this two-qubit gate matches it, otherwise return the input gate. + + """ + from opensquirrel.default_instructions import ( + default_two_qubit_gate_set, + default_two_qubit_gate_with_param_set, + ) + + if gate.name in default_two_qubit_gate_set: + return gate + + for gate_name in default_two_qubit_gate_set: + arguments: tuple[Any, ...] = (gate.qubit0, gate.qubit1) + if gate_name in default_two_qubit_gate_with_param_set and gate.controlled: + arguments += (Float(gate.controlled.target_bsr.angle),) + + try: + possible_gate = default_two_qubit_gate_set[gate_name](*arguments) # ty: ignore[invalid-argument-type] + except (TypeError, ValueError): + continue + + if possible_gate == gate: + return possible_gate + + return gate class TwoQubitGate(Gate): diff --git a/opensquirrel/writer/writer.py b/opensquirrel/writer/writer.py index 23e69938..32ea4244 100644 --- a/opensquirrel/writer/writer.py +++ b/opensquirrel/writer/writer.py @@ -29,7 +29,7 @@ ) from opensquirrel.ir.single_qubit_gate import SingleQubitGate, try_match_replace_with_default_gate from opensquirrel.ir.three_qubit_gate import ThreeQubitGate -from opensquirrel.ir.two_qubit_gate import TwoQubitGate +from opensquirrel.ir.two_qubit_gate import TwoQubitGate, try_match_replace_with_default_two_qubit_gate from opensquirrel.register_manager import RegisterManager @@ -88,6 +88,7 @@ def visit_single_qubit_gate(self, gate: SingleQubitGate) -> None: self.output += f"{gate.name}{bsr_parameters} {qubit_operand}\n" def visit_two_qubit_gate(self, gate: TwoQubitGate) -> Any: + gate = try_match_replace_with_default_two_qubit_gate(gate) qubit_operand_0 = gate.qubit0.accept(self) qubit_operand_1 = gate.qubit1.accept(self) diff --git a/tests/writer/test_writer.py b/tests/writer/test_writer.py index 002a9430..02a056d1 100644 --- a/tests/writer/test_writer.py +++ b/tests/writer/test_writer.py @@ -2,7 +2,7 @@ from opensquirrel import CircuitBuilder from opensquirrel.ir.expression import Axis -from opensquirrel.ir.semantics import BlochSphereRotation, ControlledGateSemantic, MatrixGateSemantic +from opensquirrel.ir.semantics import BlochSphereRotation, ControlledGateSemantic from opensquirrel.ir.single_qubit_gate import SingleQubitGate from opensquirrel.ir.two_qubit_gate import TwoQubitGate from opensquirrel.writer import writer @@ -213,9 +213,7 @@ def test_anonymous_gate() -> None: gate_semantic=ControlledGateSemantic(target_bsr=BlochSphereRotation(axis=(1, 1, 1), angle=1.23, phase=0.0)), ) ) - builder.ir.add_gate( - TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])) - ) + builder.CR(0, 1, 1.234) assert ( str(builder.to_circuit()) @@ -227,7 +225,6 @@ def test_anonymous_gate() -> None: H q[0] Rn(0.57735027, 0.57735027, 0.57735027, 1.23, 0.0) q[0] TwoQubitGate(qubits=[(Qubit[0], Qubit[1])], gate_semantic=ControlledGateSemantic(target_bsr=BlochSphereRotation(axis=[0.57735 0.57735 0.57735], angle=1.23, phase=0.0))) -TwoQubitGate(qubits=[(Qubit[0], Qubit[1])], gate_semantic=MatrixGateSemantic(matrix=[[1.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 1.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 1.+0.j] [0.+0.j 0.+0.j 1.+0.j 0.+0.j]])) CR(1.234) q[0], q[1] """ # ruff: ignore[line-too-long] ) From 74cf259ae87738af83bc302d2580f76c012f3974 Mon Sep 17 00:00:00 2001 From: David Bakker Date: Fri, 23 Oct 2026 15:08:13 +0200 Subject: [PATCH 2/3] Added tests --- opensquirrel/ir/two_qubit_gate.py | 25 ++++++++++-------- tests/ir/test_two_qubit_gate.py | 43 +++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/opensquirrel/ir/two_qubit_gate.py b/opensquirrel/ir/two_qubit_gate.py index 0237238a..08c8b760 100644 --- a/opensquirrel/ir/two_qubit_gate.py +++ b/opensquirrel/ir/two_qubit_gate.py @@ -5,8 +5,12 @@ import numpy as np -from opensquirrel.ir import Float, Gate, IRVisitor, Qubit, QubitLike -from opensquirrel.ir.semantics import CanonicalGateSemantic, ControlledGateSemantic, MatrixGateSemantic +from opensquirrel.ir import Gate, IRVisitor, Qubit, QubitLike +from opensquirrel.ir.semantics import ( + CanonicalGateSemantic, + ControlledGateSemantic, + MatrixGateSemantic, +) from opensquirrel.ir.semantics.bsr import bsr_from_matrix if TYPE_CHECKING: @@ -39,16 +43,15 @@ def try_match_replace_with_default_two_qubit_gate(gate: TwoQubitGate) -> TwoQubi if gate.name in default_two_qubit_gate_set: return gate - for gate_name in default_two_qubit_gate_set: - arguments: tuple[Any, ...] = (gate.qubit0, gate.qubit1) - if gate_name in default_two_qubit_gate_with_param_set and gate.controlled: - arguments += (Float(gate.controlled.target_bsr.angle),) - - try: - possible_gate = default_two_qubit_gate_set[gate_name](*arguments) # ty: ignore[invalid-argument-type] - except (TypeError, ValueError): - continue + default_gates = { + gate_name: gate_type + for gate_name, gate_type in default_two_qubit_gate_set.items() + if gate_name not in default_two_qubit_gate_with_param_set + } + for gate_type in default_gates.values(): + arguments: tuple[Any, ...] = (gate.qubit0, gate.qubit1) + possible_gate = gate_type(*arguments) # ty: ignore[missing-argument] if possible_gate == gate: return possible_gate diff --git a/tests/ir/test_two_qubit_gate.py b/tests/ir/test_two_qubit_gate.py index 56bb6fcd..0b147be1 100644 --- a/tests/ir/test_two_qubit_gate.py +++ b/tests/ir/test_two_qubit_gate.py @@ -2,13 +2,52 @@ import pytest from numpy.typing import NDArray -from opensquirrel import CNOT, CV, DCNOT, ISWAP, SWAP, InvSqrtSWAP, SqrtISWAP, SqrtSWAP +from opensquirrel import CNOT, CV, CY, CZ, DCNOT, ECR, ISWAP, MS, SWAP, InvSqrtSWAP, M, SqrtISWAP, SqrtSWAP from opensquirrel.ir import Qubit from opensquirrel.ir.semantics import BlochSphereRotation, CanonicalGateSemantic, MatrixGateSemantic -from opensquirrel.ir.two_qubit_gate import TwoQubitGate +from opensquirrel.ir.two_qubit_gate import TwoQubitGate, try_match_replace_with_default_two_qubit_gate from opensquirrel.utils.matrix_expander import can2, get_matrix +@pytest.mark.parametrize( + ("gate", "default_gate"), + [ + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(CNOT(1, 0), 2))), CNOT(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(CV(1, 0), 2))), CV(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(CY(1, 0), 2))), CY(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(CZ(1, 0), 2))), CZ(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(DCNOT(1, 0), 2))), DCNOT(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(ECR(1, 0), 2))), ECR(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(InvSqrtSWAP(1, 0), 2))), InvSqrtSWAP(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(ISWAP(1, 0), 2))), ISWAP(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(M(1, 0), 2))), M(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(MS(1, 0), 2))), MS(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(SqrtISWAP(1, 0), 2))), SqrtISWAP(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(SqrtSWAP(1, 0), 2))), SqrtSWAP(0, 1)), + (TwoQubitGate(0, 1, gate_semantic=MatrixGateSemantic(get_matrix(SWAP(1, 0), 2))), SWAP(0, 1)), + ], + ids=[ + "CNOT", + "CV", + "CY", + "CZ", + "DCNOT", + "ECR", + "InvSqrtSWAP", + "ISWAP", + "M", + "MS", + "SqrtISWAP", + "SqrtSWAP", + "SWAP", + ], +) +def test_default_gate_matching(gate: TwoQubitGate, default_gate: TwoQubitGate) -> None: + matched_gate = try_match_replace_with_default_two_qubit_gate(gate) + assert matched_gate == default_gate + assert matched_gate.name == default_gate.name + + class TestTwoQubitGate: @pytest.fixture def gate(self) -> TwoQubitGate: From b604a00d56776580d1bd91c7192486111bff656d Mon Sep 17 00:00:00 2001 From: David Bakker Date: Wed, 28 Oct 2026 15:25:15 +0100 Subject: [PATCH 3/3] process review comments --- opensquirrel/ir/two_qubit_gate.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/opensquirrel/ir/two_qubit_gate.py b/opensquirrel/ir/two_qubit_gate.py index 08c8b760..3fa3c38c 100644 --- a/opensquirrel/ir/two_qubit_gate.py +++ b/opensquirrel/ir/two_qubit_gate.py @@ -19,14 +19,13 @@ def try_match_replace_with_default_two_qubit_gate(gate: TwoQubitGate) -> TwoQubitGate: """Tries to match a given two-qubit gate with a _default_ two-qubit gate. - It does that by checking if the parameters of the Bloch sphere rotation (BSR) semantic of the - input two-qubit gate are close to those of any of the default two-qubit gates. + It does that by checking if the two-qubit gates matches one of the _default_ two-qubit gates. Note: - The default (two-qubit) gates are defined in the [cQASM standard gate set](https://qutech-delft.github.io/cQASM-spec/latest/standard_gate_set/index.html). - - If no specific match is found, the general Rn gate is returned with the same parameters - values as those of the input gate. + - If no specific match is found, the input gate is returned unmodified. + - The parameterized two-qubit gates (e.g., CR, CRk) are not included in the default two-qubit gate set. Args: gate: The two-qubit gate to be matched.