diff --git a/opensquirrel/circuit_builder.py b/opensquirrel/circuit_builder.py index fddb5abc..32639aab 100644 --- a/opensquirrel/circuit_builder.py +++ b/opensquirrel/circuit_builder.py @@ -8,7 +8,7 @@ from opensquirrel.circuit import Circuit from opensquirrel.default_instructions import default_instruction_set -from opensquirrel.ir import IR, AsmDeclaration, Bit, BitLike, Instruction, Measure, Qubit, QubitLike +from opensquirrel.ir import IR, AsmDeclaration, Bit, BitLike, Instruction, Qubit, QubitLike from opensquirrel.register_manager import BitRegister, QubitRegister, RegisterManager _builder_dynamic_attributes = (*default_instruction_set, "asm") @@ -75,12 +75,11 @@ def _check_bit_out_of_bounds_access(self, bit: BitLike) -> None: raise IndexError(msg) def _check_out_of_bounds_access(self, instruction: Instruction) -> None: - for qubit in instruction.get_qubit_operands(): + for qubit in instruction.qubit_operands: self._check_qubit_out_of_bounds_access(qubit) - if isinstance(instruction, Measure): - for bit in instruction.get_bit_operands(): - self._check_bit_out_of_bounds_access(bit) + for bit in instruction.bit_operands: + self._check_bit_out_of_bounds_access(bit) def _add_statement(self, attr: str, *args: Any) -> Self: if attr == "asm": diff --git a/opensquirrel/ir/control_instruction.py b/opensquirrel/ir/control_instruction.py index 9712f332..201bcee3 100644 --- a/opensquirrel/ir/control_instruction.py +++ b/opensquirrel/ir/control_instruction.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import Any, SupportsInt -from opensquirrel.ir.expression import Expression, Int, Qubit, QubitLike +from opensquirrel.ir.expression import Bit, Expression, Int, Qubit, QubitLike from opensquirrel.ir.ir import IRVisitor from opensquirrel.ir.statement import Instruction @@ -16,8 +16,13 @@ def __init__(self, qubit: QubitLike, name: str) -> None: def arguments(self) -> tuple[Expression, ...]: pass - def get_qubit_operands(self) -> list[Qubit]: - return [self.qubit] + @property + def qubit_operands(self) -> tuple[Qubit, ...]: + return (self.qubit,) + + @property + def bit_operands(self) -> tuple[Bit, ...]: + return () class Barrier(ControlInstruction): diff --git a/opensquirrel/ir/default_gates/two_qubit_gates.py b/opensquirrel/ir/default_gates/two_qubit_gates.py index bd3331b2..d8f21eee 100644 --- a/opensquirrel/ir/default_gates/two_qubit_gates.py +++ b/opensquirrel/ir/default_gates/two_qubit_gates.py @@ -41,13 +41,6 @@ def __init__(self, qubit_0: QubitLike, qubit_1: QubitLike) -> None: self.qubit_0 = Qubit(qubit_0) self.qubit_1 = Qubit(qubit_1) - @property - def arguments(self) -> tuple[Expression, ...]: - return self.qubit_0, self.qubit_1 - - def get_qubit_operands(self) -> list[Qubit]: - return [self.qubit_0, self.qubit_1] - class CNOT(TwoQubitGate): def __init__(self, control_qubit: QubitLike, target_qubit: QubitLike) -> None: diff --git a/opensquirrel/ir/non_unitary.py b/opensquirrel/ir/non_unitary.py index 508ab825..8e79ddf8 100644 --- a/opensquirrel/ir/non_unitary.py +++ b/opensquirrel/ir/non_unitary.py @@ -19,8 +19,13 @@ def __init__(self, qubit: QubitLike, name: str) -> None: def arguments(self) -> tuple[Expression, ...]: pass - def get_qubit_operands(self) -> list[Qubit]: - return [self.qubit] + @property + def qubit_operands(self) -> tuple[Qubit, ...]: + return (self.qubit,) + + @property + def bit_operands(self) -> tuple[Bit, ...]: + return () def accept(self, visitor: IRVisitor) -> Any: return visitor.visit_non_unitary(self) @@ -49,8 +54,9 @@ def accept(self, visitor: IRVisitor) -> Any: non_unitary_visit = super().accept(visitor) return non_unitary_visit if non_unitary_visit is not None else visitor.visit_measure(self) - def get_bit_operands(self) -> list[Bit]: - return [self.bit] + @property + def bit_operands(self) -> tuple[Bit, ...]: + return (self.bit,) class Init(NonUnitary): diff --git a/opensquirrel/ir/single_qubit_gate.py b/opensquirrel/ir/single_qubit_gate.py index 8bc1e1a2..dba74f29 100644 --- a/opensquirrel/ir/single_qubit_gate.py +++ b/opensquirrel/ir/single_qubit_gate.py @@ -3,10 +3,11 @@ from functools import cached_property from typing import TYPE_CHECKING, Any -from opensquirrel.ir import Float, Gate, GateSemantic, Qubit, QubitLike +from opensquirrel.ir import Bit, Float, Gate, GateSemantic, Qubit, QubitLike from opensquirrel.ir.semantics import BlochSphereRotation, MatrixGateSemantic if TYPE_CHECKING: + from opensquirrel.ir.expression import Expression from opensquirrel.ir.ir import IRVisitor @@ -84,11 +85,16 @@ def __mul__(self, other: SingleQubitGate) -> SingleQubitGate: return SingleQubitGate(self.qubit, self.bsr * other.bsr) @property - def arguments(self) -> tuple[Qubit, ...]: + def arguments(self) -> tuple[Expression, ...]: + return () + + @property + def qubit_operands(self) -> tuple[Qubit, ...]: return (self.qubit,) - def get_qubit_operands(self) -> list[Qubit]: - return [self.qubit] + @property + def bit_operands(self) -> tuple[Bit, ...]: + return () def is_identity(self) -> bool: if self.bsr is not None: diff --git a/opensquirrel/ir/statement.py b/opensquirrel/ir/statement.py index ca2f4987..11d97a8c 100644 --- a/opensquirrel/ir/statement.py +++ b/opensquirrel/ir/statement.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import Any -from opensquirrel.ir.expression import Expression, Qubit, String, SupportsStr +from opensquirrel.ir.expression import Bit, Expression, Qubit, String, SupportsStr from opensquirrel.ir.ir import IRNode, IRVisitor @@ -40,8 +40,18 @@ def __init__(self, name: str) -> None: def arguments(self) -> tuple[Expression, ...]: pass + @property + @abstractmethod + def qubit_operands(self) -> tuple[Qubit, ...]: + pass + + @property + def qubit_indices(self) -> list[int]: + return [qubit.index for qubit in self.qubit_operands] + + @property @abstractmethod - def get_qubit_operands(self) -> list[Qubit]: + def bit_operands(self) -> tuple[Bit, ...]: pass def accept(self, visitor: IRVisitor) -> Any: diff --git a/opensquirrel/ir/two_qubit_gate.py b/opensquirrel/ir/two_qubit_gate.py index 63e3267b..79a05f10 100644 --- a/opensquirrel/ir/two_qubit_gate.py +++ b/opensquirrel/ir/two_qubit_gate.py @@ -3,7 +3,7 @@ import numpy as np -from opensquirrel.ir import Gate, IRVisitor, Qubit, QubitLike +from opensquirrel.ir import Bit, Gate, IRVisitor, Qubit, QubitLike from opensquirrel.ir.expression import Expression from opensquirrel.ir.semantics import CanonicalGateSemantic, ControlledGateSemantic, MatrixGateSemantic from opensquirrel.ir.semantics.gate_semantic import GateSemantic @@ -27,7 +27,7 @@ def __init__( msg = "the qubit from the target gate does not match with 'qubit1'." raise ValueError(msg) - if self._check_repeated_qubit_operands([self.qubit0, self.qubit1]): + if self._check_repeated_qubit_operands(self.qubit_operands): msg = "qubit0 and qubit1 cannot be the same" raise ValueError(msg) @@ -63,10 +63,15 @@ def accept(self, visitor: IRVisitor) -> Any: @property def arguments(self) -> tuple[Expression, ...]: + return () + + @property + def qubit_operands(self) -> tuple[Qubit, ...]: return (self.qubit0, self.qubit1) - def get_qubit_operands(self) -> list[Qubit]: - return [self.qubit0, self.qubit1] + @property + def bit_operands(self) -> tuple[Bit, ...]: + return () def is_identity(self) -> bool: if self.controlled: diff --git a/opensquirrel/ir/unitary.py b/opensquirrel/ir/unitary.py index 9e85af57..f0cc6987 100644 --- a/opensquirrel/ir/unitary.py +++ b/opensquirrel/ir/unitary.py @@ -25,10 +25,6 @@ def __init__(self, name: str) -> None: def _check_repeated_qubit_operands(qubits: Sequence[Qubit]) -> bool: return len(qubits) != len(set(qubits)) - @abstractmethod - def get_qubit_operands(self) -> list[Qubit]: - pass - @abstractmethod def is_identity(self) -> bool: pass @@ -43,7 +39,7 @@ def __eq__(self, other: object) -> bool: def compare_gates(g1: Gate, g2: Gate) -> bool: - union_mapping = [q.index for q in list(set(g1.get_qubit_operands()) | set(g2.get_qubit_operands()))] + union_mapping = [q.index for q in list(set(g1.qubit_operands) | set(g2.qubit_operands))] from opensquirrel.circuit_matrix_calculator import get_circuit_matrix from opensquirrel.reindexer import get_reindexed_circuit diff --git a/opensquirrel/passes/decomposer/cnot2cz_decomposer.py b/opensquirrel/passes/decomposer/cnot2cz_decomposer.py index b8fc1c78..6452ed0e 100644 --- a/opensquirrel/passes/decomposer/cnot2cz_decomposer.py +++ b/opensquirrel/passes/decomposer/cnot2cz_decomposer.py @@ -25,7 +25,7 @@ def decompose(self, gate: Gate) -> list[Gate]: if gate.name != "CNOT": return [gate] - control_qubit, target_qubit = gate.get_qubit_operands() + control_qubit, target_qubit = gate.qubit_operands return [ Ry(target_qubit, -pi / 2), CZ(control_qubit, target_qubit), diff --git a/opensquirrel/passes/decomposer/cz_decomposer.py b/opensquirrel/passes/decomposer/cz_decomposer.py index c4aee0f5..b55c6782 100644 --- a/opensquirrel/passes/decomposer/cz_decomposer.py +++ b/opensquirrel/passes/decomposer/cz_decomposer.py @@ -32,7 +32,7 @@ def decompose(self, g: Gate) -> list[Gate]: # - decomposing MatrixGate is currently not supported. return [g] - control_qubit, target_qubit = g.get_qubit_operands() + control_qubit, target_qubit = g.qubit_operands target_gate = g.controlled.target_gate # Perform XYX decomposition on the target gate. diff --git a/opensquirrel/passes/decomposer/general_decomposer.py b/opensquirrel/passes/decomposer/general_decomposer.py index 72bea18d..1a7fb09f 100644 --- a/opensquirrel/passes/decomposer/general_decomposer.py +++ b/opensquirrel/passes/decomposer/general_decomposer.py @@ -20,7 +20,7 @@ def decompose(self, gate: Gate) -> list[Gate]: def check_gate_replacement(gate: Gate, replacement_gates: Iterable[Gate]) -> None: - gate_qubit_indices = [q.index for q in gate.get_qubit_operands()] + gate_qubit_indices = gate.qubit_indices replacement_gates_qubit_indices = set() replaced_matrix = get_circuit_matrix(get_reindexed_circuit([gate], gate_qubit_indices)) @@ -28,7 +28,7 @@ def check_gate_replacement(gate: Gate, replacement_gates: Iterable[Gate]) -> Non return for g in replacement_gates: - replacement_gates_qubit_indices.update([q.index for q in g.get_qubit_operands()]) + replacement_gates_qubit_indices.update(g.qubit_indices) if set(gate_qubit_indices) != replacement_gates_qubit_indices: msg = f"replacement for gate {gate.name} does not seem to operate on the right qubits" @@ -70,7 +70,7 @@ def __init__(self, gate_type: type[Gate], replacement_gates_function: Callable[. def decompose(self, gate: Gate) -> list[Gate]: if is_anonymous_gate(gate.name) or type(gate) is not self.gate_type: return [gate] - return self.replacement_gates_function(*gate.arguments) + return self.replacement_gates_function(*gate.qubit_operands, *gate.arguments) def replace(ir: IR, gate: type[Gate], replacement_gates_function: Callable[..., list[Gate]]) -> None: diff --git a/opensquirrel/passes/decomposer/swap2cnot_decomposer.py b/opensquirrel/passes/decomposer/swap2cnot_decomposer.py index 31c677ca..43d8869d 100644 --- a/opensquirrel/passes/decomposer/swap2cnot_decomposer.py +++ b/opensquirrel/passes/decomposer/swap2cnot_decomposer.py @@ -21,7 +21,7 @@ class SWAP2CNOTDecomposer(Decomposer): def decompose(self, gate: Gate) -> list[Gate]: if gate.name != "SWAP": return [gate] - qubit0, qubit1 = gate.get_qubit_operands() + qubit0, qubit1 = gate.qubit_operands return [ CNOT(qubit0, qubit1), CNOT(qubit1, qubit0), diff --git a/opensquirrel/passes/decomposer/swap2cz_decomposer.py b/opensquirrel/passes/decomposer/swap2cz_decomposer.py index 3eaac714..b89e27aa 100644 --- a/opensquirrel/passes/decomposer/swap2cz_decomposer.py +++ b/opensquirrel/passes/decomposer/swap2cz_decomposer.py @@ -22,7 +22,7 @@ class SWAP2CZDecomposer(Decomposer): def decompose(self, gate: Gate) -> list[Gate]: if gate.name != "SWAP": return [gate] - qubit0, qubit1 = gate.get_qubit_operands() + qubit0, qubit1 = gate.qubit_operands return [ Ry(qubit1, -pi / 2), CZ(qubit0, qubit1), diff --git a/opensquirrel/passes/exporter/cqasmv1_exporter/cqasmv1_exporter.py b/opensquirrel/passes/exporter/cqasmv1_exporter/cqasmv1_exporter.py index 39ccb170..e3c7fb2c 100644 --- a/opensquirrel/passes/exporter/cqasmv1_exporter/cqasmv1_exporter.py +++ b/opensquirrel/passes/exporter/cqasmv1_exporter/cqasmv1_exporter.py @@ -107,14 +107,11 @@ def visit_two_qubit_gate(self, gate: TwoQubitGate) -> Any: qubit_operand_0 = gate.qubit0.accept(self) qubit_operand_1 = gate.qubit1.accept(self) - if len(gate.arguments) == 2: - self.output += f"{gate.name.lower()} {qubit_operand_0}, {qubit_operand_1}\n" - elif len(gate.arguments) == 3: - _, _, arg = gate.arguments - argument = arg.accept(self) - self.output += f"{gate.name.lower()}({argument}) {qubit_operand_0}, {qubit_operand_1}\n" + if gate.arguments: + arguments = ", ".join(arg.accept(self) for arg in gate.arguments) + self.output += f"{gate.name.lower()}({arguments}) {qubit_operand_0}, {qubit_operand_1}\n" else: - raise UnsupportedGateError(gate) + self.output += f"{gate.name.lower()} {qubit_operand_0}, {qubit_operand_1}\n" def visit_cr(self, gate: CR) -> None: control_qubit_operand = gate.control_qubit.accept(self) diff --git a/opensquirrel/passes/exporter/quantify_scheduler_exporter.py b/opensquirrel/passes/exporter/quantify_scheduler_exporter.py index 3b24c1df..d7deeb52 100644 --- a/opensquirrel/passes/exporter/quantify_scheduler_exporter.py +++ b/opensquirrel/passes/exporter/quantify_scheduler_exporter.py @@ -191,12 +191,10 @@ def operation_record(self) -> OperationRecord: return self._operation_record def visit_gate(self, gate: Gate) -> None: - qubit_indices = [qubit.index for qubit in gate.get_qubit_operands()] - self._operation_record.set_schedulable_timing_constraints(qubit_indices) + self._operation_record.set_schedulable_timing_constraints(gate.qubit_indices) def visit_non_unitary(self, non_unitary: NonUnitary) -> None: - qubit_indices = [qubit.index for qubit in non_unitary.get_qubit_operands()] - self._operation_record.set_schedulable_timing_constraints(qubit_indices) + self._operation_record.set_schedulable_timing_constraints(non_unitary.qubit_indices) def visit_control_instruction(self, control_instruction: ControlInstruction) -> None: if isinstance(control_instruction, Wait): @@ -228,7 +226,7 @@ def visit_single_qubit_gate(self, gate: SingleQubitGate) -> None: # Hadamard gate. self.schedule.add( quantify_scheduler.operations.gate_library.H(self._get_qubit_string(gate.qubit)), - label=self._get_operation_label("H", gate.get_qubit_operands()), + label=self._get_operation_label("H", gate.qubit_operands), ) return if abs(gate.bsr.axis[2]) < ATOL: @@ -240,7 +238,7 @@ def visit_single_qubit_gate(self, gate: SingleQubitGate) -> None: quantify_scheduler.operations.gate_library.Rxy( theta=theta, phi=phi, qubit=self._get_qubit_string(gate.qubit) ), - label=self._get_operation_label("Rxy", gate.get_qubit_operands()), + label=self._get_operation_label("Rxy", gate.qubit_operands), ) return if abs(gate.bsr.axis[0]) < ATOL and abs(gate.bsr.axis[1]) < ATOL: @@ -248,7 +246,7 @@ def visit_single_qubit_gate(self, gate: SingleQubitGate) -> None: theta = round(math.degrees(gate.bsr.angle), FIXED_POINT_DEG_PRECISION) self.schedule.add( quantify_scheduler.operations.gate_library.Rz(theta=theta, qubit=self._get_qubit_string(gate.qubit)), - label=self._get_operation_label("Rz", gate.get_qubit_operands()), + label=self._get_operation_label("Rz", gate.qubit_operands), ) return raise UnsupportedGateError(gate) @@ -257,7 +255,7 @@ def visit_two_qubit_gate(self, gate: TwoQubitGate) -> Any: if gate.name not in ["CNOT", "CZ"]: raise UnsupportedGateError(gate) - control_qubit, target_qubit = gate.get_qubit_operands() + control_qubit, target_qubit = gate.qubit_operands if gate.name == "CNOT": self.schedule.add( @@ -265,7 +263,7 @@ def visit_two_qubit_gate(self, gate: TwoQubitGate) -> Any: qC=self._get_qubit_string(control_qubit), qT=self._get_qubit_string(target_qubit), ), - label=self._get_operation_label("CNOT", gate.get_qubit_operands()), + label=self._get_operation_label("CNOT", gate.qubit_operands), ) if gate.name == "CZ": self.schedule.add( @@ -273,7 +271,7 @@ def visit_two_qubit_gate(self, gate: TwoQubitGate) -> Any: qC=self._get_qubit_string(control_qubit), qT=self._get_qubit_string(target_qubit), ), - label=self._get_operation_label("CZ", gate.get_qubit_operands()), + label=self._get_operation_label("CZ", gate.qubit_operands), ) def visit_measure(self, gate: Measure) -> None: @@ -286,7 +284,7 @@ def visit_measure(self, gate: Measure) -> None: acq_index=acq_index, acq_protocol="ThresholdedAcquisition", ), - label=self._get_operation_label("Measure", gate.get_qubit_operands()), + label=self._get_operation_label("Measure", gate.qubit_operands), ) self.measurement_index_record[qubit_index] += 1 @@ -296,12 +294,12 @@ def visit_init(self, gate: Init) -> None: def visit_reset(self, gate: Reset) -> None: self.schedule.add( quantify_scheduler.operations.gate_library.Reset(self._get_qubit_string(gate.qubit)), - label=self._get_operation_label("Reset", gate.get_qubit_operands()), + label=self._get_operation_label("Reset", gate.qubit_operands), ) def _get_qubit_string(self, qubit: Qubit) -> str: return f"{self.circuit.qubit_register_name}[{qubit.index}]" - def _get_operation_label(self, name: str, qubits: list[Qubit]) -> str: + def _get_operation_label(self, name: str, qubits: tuple[Qubit, ...]) -> str: qubit_operands = ", ".join([self._get_qubit_string(qubit) for qubit in qubits]) return f"{name} {qubit_operands} | " + str(uuid4()) diff --git a/opensquirrel/passes/mapper/mip_mapper.py b/opensquirrel/passes/mapper/mip_mapper.py index 26759f45..00ac8c4d 100644 --- a/opensquirrel/passes/mapper/mip_mapper.py +++ b/opensquirrel/passes/mapper/mip_mapper.py @@ -7,12 +7,13 @@ import numpy as np from scipy.optimize import Bounds, LinearConstraint, milp -from opensquirrel.ir import IR, Instruction, Qubit +from opensquirrel.ir.two_qubit_gate import TwoQubitGate from opensquirrel.passes.mapper.general_mapper import Mapper from opensquirrel.passes.mapper.mapping import Mapping if TYPE_CHECKING: from opensquirrel import Connectivity + from opensquirrel.ir import IR DISTANCE_UL = 999999 @@ -90,13 +91,11 @@ def _get_cost( ) -> list[list[int]]: reference_counter = [[0 for _ in range(num_virtual_qubits)] for _ in range(num_virtual_qubits)] for statement in getattr(ir, "statements", []): - if isinstance(statement, Instruction): - args = statement.arguments - if args and len(args) > 1 and all(isinstance(arg, Qubit) for arg in args): - qubit_args = [arg for arg in args if isinstance(arg, Qubit)] - for q_0, q_1 in itertools.pairwise(qubit_args): - reference_counter[q_0.index][q_1.index] += 1 - reference_counter[q_1.index][q_0.index] += 1 + if isinstance(statement, TwoQubitGate): + for q_0, q_1 in itertools.pairwise(statement.qubit_operands): + reference_counter[q_0.index][q_1.index] += 1 + reference_counter[q_1.index][q_0.index] += 1 + cost = [[0 for _ in range(num_physical_qubits)] for _ in range(num_virtual_qubits)] for i in range(num_virtual_qubits): for k in range(num_physical_qubits): diff --git a/opensquirrel/passes/mapper/qgym_mapper.py b/opensquirrel/passes/mapper/qgym_mapper.py index 95c110f1..0ffa264b 100644 --- a/opensquirrel/passes/mapper/qgym_mapper.py +++ b/opensquirrel/passes/mapper/qgym_mapper.py @@ -124,7 +124,7 @@ def _ir_to_interaction_graph(ir: IR) -> nx.Graph: if not isinstance(statement, Instruction): continue instruction = cast("Instruction", statement) # type: ignore[redundant-cast] - qubit_indices = [q.index for q in instruction.get_qubit_operands()] + qubit_indices = instruction.qubit_indices for q_index in qubit_indices: interaction_graph.add_node(q_index) if len(qubit_indices) >= 2: diff --git a/opensquirrel/passes/mapper/qubit_remapper.py b/opensquirrel/passes/mapper/qubit_remapper.py index 60cafd91..eddb525f 100644 --- a/opensquirrel/passes/mapper/qubit_remapper.py +++ b/opensquirrel/passes/mapper/qubit_remapper.py @@ -52,7 +52,7 @@ def visit_single_qubit_gate(self, gate: SingleQubitGate) -> SingleQubitGate: return gate def visit_two_qubit_gate(self, gate: TwoQubitGate) -> TwoQubitGate: - for operand in gate.get_qubit_operands(): + for operand in gate.qubit_operands: operand.accept(self) if gate.controlled is not None: diff --git a/opensquirrel/passes/mapper/utils.py b/opensquirrel/passes/mapper/utils.py index 376b865a..8a9a7255 100644 --- a/opensquirrel/passes/mapper/utils.py +++ b/opensquirrel/passes/mapper/utils.py @@ -8,7 +8,7 @@ def make_interaction_graph(ir: IR) -> nx.Graph: gates = (statement for statement in ir.statements if isinstance(statement, Gate)) for gate in gates: - target_qubits = gate.get_qubit_operands() + target_qubits = gate.qubit_operands match len(target_qubits): case 1: continue diff --git a/opensquirrel/passes/merger/general_merger.py b/opensquirrel/passes/merger/general_merger.py index 69760883..e77dfa21 100644 --- a/opensquirrel/passes/merger/general_merger.py +++ b/opensquirrel/passes/merger/general_merger.py @@ -19,9 +19,8 @@ def can_move_statement_before_barrier(instruction: Instruction, barriers: list[I """Checks whether an instruction can be moved before a group of 'linked' barriers. Returns True if none of the qubits used by the instruction are part of any barrier, False otherwise. """ - instruction_qubit_operands = instruction.get_qubit_operands() - barriers_group_qubit_operands = set(flatten_list([barrier.get_qubit_operands() for barrier in barriers])) - return not any(qubit in barriers_group_qubit_operands for qubit in instruction_qubit_operands) + barriers_group_qubit_operands = set(flatten_list([list(barrier.qubit_operands) for barrier in barriers])) + return not any(qubit in barriers_group_qubit_operands for qubit in instruction.qubit_operands) def can_move_before(statement: Statement, statement_group: list[Statement]) -> bool: diff --git a/opensquirrel/passes/merger/single_qubit_gates_merger.py b/opensquirrel/passes/merger/single_qubit_gates_merger.py index 7381b2b8..0e239d3b 100644 --- a/opensquirrel/passes/merger/single_qubit_gates_merger.py +++ b/opensquirrel/passes/merger/single_qubit_gates_merger.py @@ -1,3 +1,4 @@ +from collections.abc import Iterable from typing import cast from opensquirrel import I @@ -33,7 +34,7 @@ def merge(self, ir: IR, qubit_register_size: int) -> None: del ir.statements[statement_index] continue - def insert_accumulated_bloch_sphere_rotations(qubits: list[Qubit]) -> None: + def insert_accumulated_bloch_sphere_rotations(qubits: Iterable[Qubit]) -> None: nonlocal statement_index for qubit in qubits: if not accumulators_per_qubit[qubit].is_identity(): @@ -47,7 +48,7 @@ def insert_accumulated_bloch_sphere_rotations(qubits: list[Qubit]) -> None: if isinstance(instruction, Barrier) or isinstance(statement, AsmDeclaration): insert_accumulated_bloch_sphere_rotations([Qubit(i) for i in range(qubit_register_size)]) else: - insert_accumulated_bloch_sphere_rotations(instruction.get_qubit_operands()) + insert_accumulated_bloch_sphere_rotations(instruction.qubit_operands) statement_index += 1 for accumulated_bloch_sphere_rotation in accumulators_per_qubit.values(): diff --git a/opensquirrel/passes/router/common.py b/opensquirrel/passes/router/common.py index 19376dec..44f151ed 100644 --- a/opensquirrel/passes/router/common.py +++ b/opensquirrel/passes/router/common.py @@ -1,14 +1,15 @@ from __future__ import annotations import itertools -from collections.abc import Callable +from collections.abc import Callable, Iterable from typing import TYPE_CHECKING import networkx as nx from opensquirrel import SWAP from opensquirrel.exceptions import NoRoutingPathError -from opensquirrel.ir import IR, Gate, Instruction, Statement +from opensquirrel.ir import IR, Instruction, Statement +from opensquirrel.ir.two_qubit_gate import TwoQubitGate if TYPE_CHECKING: from opensquirrel import Connectivity @@ -89,8 +90,8 @@ def _plan_swaps( temp_mapping = initial_mapping.copy() for statement_index, statement in enumerate(ir.statements): - if isinstance(statement, Gate) and len(statement.get_qubit_operands()) == 2: - q0, q1 = statement.get_qubit_operands() + if isinstance(statement, TwoQubitGate): + q0, q1 = statement.qubit_operands physical_q0_index = temp_mapping[q0.index] physical_q1_index = temp_mapping[q1.index] @@ -129,19 +130,18 @@ def _apply_swaps(ir: IR, planned_swaps: dict[int, SWAP], initial_mapping: dict[i for statement in ir.statements: while len(new_ir_statements) in planned_swaps: swap_gate = planned_swaps[len(new_ir_statements)] - swap_qubit_indices = tuple(qubit.index for qubit in swap_gate.get_qubit_operands()) - ProcessSwaps._update_mapping_for_swap(new_mapping, swap_qubit_indices) + ProcessSwaps._update_mapping_for_swap(new_mapping, swap_gate.qubit_indices) new_ir_statements.append(swap_gate) if isinstance(statement, Instruction): - for qubit in statement.get_qubit_operands(): + for qubit in statement.qubit_operands: qubit.index = new_mapping[qubit.index] new_ir_statements.append(statement) return new_ir_statements @staticmethod - def _update_mapping_for_swap(mapping: dict[int, int], swap_qubit_indices: tuple[int, ...]) -> None: + def _update_mapping_for_swap(mapping: dict[int, int], swap_qubit_indices: Iterable[int]) -> None: """Updates the mapping for the given swap qubit indices. Args: diff --git a/opensquirrel/passes/validator/interaction_validator.py b/opensquirrel/passes/validator/interaction_validator.py index 8beb8d83..bf1793b6 100644 --- a/opensquirrel/passes/validator/interaction_validator.py +++ b/opensquirrel/passes/validator/interaction_validator.py @@ -1,7 +1,8 @@ import itertools from typing import Any -from opensquirrel.ir import IR, Instruction, Qubit +from opensquirrel.ir import IR +from opensquirrel.ir.two_qubit_gate import TwoQubitGate from opensquirrel.passes.validator.general_validator import Validator @@ -22,15 +23,12 @@ def validate(self, ir: IR) -> None: """ non_executable_interactions = [] for statement in ir.statements: - if not isinstance(statement, Instruction): + if not isinstance(statement, TwoQubitGate): continue - args = statement.arguments - if args and len(args) > 1 and all(isinstance(arg, Qubit) for arg in args): - qubit_args = [arg for arg in args if isinstance(arg, Qubit)] - qubit_index_pairs = [(q0.index, q1.index) for q0, q1 in itertools.pairwise(qubit_args)] - for i, j in qubit_index_pairs: - if j not in self.connectivity.get(str(i), []): - non_executable_interactions.append((i, j)) + qubit_index_pairs = itertools.pairwise(statement.qubit_indices) + for i, j in qubit_index_pairs: + if j not in self.connectivity.get(str(i), []): + non_executable_interactions.append((i, j)) if non_executable_interactions: error_message = ( diff --git a/opensquirrel/utils/matrix_expander.py b/opensquirrel/utils/matrix_expander.py index a958eb16..bccb45e5 100644 --- a/opensquirrel/utils/matrix_expander.py +++ b/opensquirrel/utils/matrix_expander.py @@ -159,7 +159,7 @@ def _matrix_gate(self, gate: TwoQubitGate) -> NDArray[np.complex128]: # 0, 0, 1, 0 # which corresponds to control being q[1] and target being q[0], # since qubit #i corresponds to the i-th least significant bit. - qubit_operands = list(reversed(gate.get_qubit_operands())) + qubit_operands = list(reversed(gate.qubit_operands)) if any(q.index >= self.qubit_register_size for q in qubit_operands): msg = "index out of range" @@ -189,7 +189,7 @@ def _matrix_gate(self, gate: TwoQubitGate) -> NDArray[np.complex128]: return expanded_matrix def visit_canonical_gate(self, gate: TwoQubitGate) -> Any: - qubit_operands = list(reversed(gate.get_qubit_operands())) + qubit_operands = list(reversed(gate.qubit_operands)) if not gate.canonical: msg = "gate needs to have a canonical representation" diff --git a/opensquirrel/writer/writer.py b/opensquirrel/writer/writer.py index e4e2f2f8..2b1d3720 100644 --- a/opensquirrel/writer/writer.py +++ b/opensquirrel/writer/writer.py @@ -2,7 +2,6 @@ from opensquirrel.circuit import Circuit from opensquirrel.default_instructions import default_two_qubit_gate_set -from opensquirrel.exceptions import UnsupportedGateError from opensquirrel.ir import ( AsmDeclaration, Barrier, @@ -85,14 +84,11 @@ def visit_two_qubit_gate(self, gate: TwoQubitGate) -> Any: if gate.name not in default_two_qubit_gate_set: self.output += f"{gate}\n" - elif len(gate.arguments) == 2: - self.output += f"{gate.name} {qubit_operand_0}, {qubit_operand_1}\n" - elif len(gate.arguments) == 3: - _, _, arg = gate.arguments - argument = arg.accept(self) - self.output += f"{gate.name}({argument}) {qubit_operand_0}, {qubit_operand_1}\n" + elif gate.arguments: + arguments = ", ".join(arg.accept(self) for arg in gate.arguments) + self.output += f"{gate.name}({arguments}) {qubit_operand_0}, {qubit_operand_1}\n" else: - raise UnsupportedGateError(gate) + self.output += f"{gate.name} {qubit_operand_0}, {qubit_operand_1}\n" def visit_bsr_no_params(self, gate: BsrNoParams) -> str: return "" diff --git a/tests/ir/test_non_unitary.py b/tests/ir/test_non_unitary.py index 7bdd1ddd..c89f0a2c 100644 --- a/tests/ir/test_non_unitary.py +++ b/tests/ir/test_non_unitary.py @@ -26,8 +26,8 @@ def test_equality(self, measure: Measure) -> None: def test_inequality(self, measure: Measure, other_measure: Measure | str) -> None: assert measure != other_measure - def test_get_bit_operands(self, measure: Measure) -> None: - assert measure.get_bit_operands() == [Bit(42)] + def test_bit_operands(self, measure: Measure) -> None: + assert measure.bit_operands == (Bit(42),) - def test_get_qubit_operands(self, measure: Measure) -> None: - assert measure.get_qubit_operands() == [Qubit(42)] + def test_qubit_operands(self, measure: Measure) -> None: + assert measure.qubit_operands == (Qubit(42),) diff --git a/tests/ir/test_two_qubit_gate.py b/tests/ir/test_two_qubit_gate.py index fdf80586..da912786 100644 --- a/tests/ir/test_two_qubit_gate.py +++ b/tests/ir/test_two_qubit_gate.py @@ -20,8 +20,8 @@ def gate(self) -> TwoQubitGate: ] return TwoQubitGate(42, 100, gate_semantic=MatrixGateSemantic(cnot_matrix)) - def test_get_qubit_operands(self, gate: TwoQubitGate) -> None: - assert gate.get_qubit_operands() == [Qubit(42), Qubit(100)] + def test_qubit_operands(self, gate: TwoQubitGate) -> None: + assert gate.qubit_operands == (Qubit(42), Qubit(100)) def test_same_qubits(self) -> None: with pytest.raises(ValueError, match="qubit0 and qubit1 cannot be the same"): diff --git a/tests/passes/router/test_astar_router.py b/tests/passes/router/test_astar_router.py index 33601da3..3c5437d3 100644 --- a/tests/passes/router/test_astar_router.py +++ b/tests/passes/router/test_astar_router.py @@ -137,6 +137,6 @@ def test_route_indices_propagation(router4: AStarRouter, circuit4: Circuit) -> N for actual, expected in zip(actual_statements, expected_statements, strict=False): assert type(actual) is type(expected) - actual_indices = [q.index for q in actual.get_qubit_operands()] # type: ignore[attr-defined] - expected_indices = [q.index for q in expected.get_qubit_operands()] # type: ignore[attr-defined] + actual_indices = actual.qubit_indices # type: ignore[attr-defined] + expected_indices = expected.qubit_indices # type: ignore[attr-defined] assert actual_indices == expected_indices diff --git a/tests/passes/router/test_shortest_path_router.py b/tests/passes/router/test_shortest_path_router.py index 33460e68..6d7c04a9 100644 --- a/tests/passes/router/test_shortest_path_router.py +++ b/tests/passes/router/test_shortest_path_router.py @@ -136,6 +136,6 @@ def test_route_indices_propagation(router4: ShortestPathRouter, circuit4: Circui for actual, expected in zip(actual_statements, expected_statements, strict=False): assert type(actual) is type(expected) - actual_indices = [q.index for q in actual.get_qubit_operands()] # type: ignore[attr-defined] - expected_indices = [q.index for q in expected.get_qubit_operands()] # type: ignore[attr-defined] + actual_indices = [q.index for q in actual.qubit_operands] # type: ignore[attr-defined] + expected_indices = [q.index for q in expected.qubit_operands] # type: ignore[attr-defined] assert actual_indices == expected_indices