Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create 2 qubit (generic) fusion pass #71 #314

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions opensquirrel/circuit_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ def __init__(
self.register_manager = RegisterManager(QubitRegister(qubit_register_size), BitRegister(bit_register_size))
self.ir = IR()

def __contains__(self, qubit:Qubit):
return qubit in self.register_manager
def __getattr__(self, attr: Any) -> Callable[..., Self]:
if attr == "comment":
return self._add_comment
Expand Down
2 changes: 2 additions & 0 deletions opensquirrel/decomposer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
)
from opensquirrel.decomposer.cnot_decomposer import CNOTDecomposer
from opensquirrel.decomposer.mckay_decomposer import McKayDecomposer
from opensquirrel.decomposer.twoqubitgate_decomposer import TwoQubitGateFolder

__all__ = [
"McKayDecomposer",
Expand All @@ -18,4 +19,5 @@
"ZXZDecomposer",
"ZYZDecomposer",
"CNOTDecomposer",
"TwoQubitGateFolder"
]
2 changes: 1 addition & 1 deletion opensquirrel/decomposer/aba_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def get_decomposition_angles(self, alpha: float, axis: AxisLike) -> tuple[float,

return theta1, theta2, theta3

def decompose(self, g: Gate) -> list[Gate]:
def decompose(self, g: Gate,gates_before : list[Statement] = [], gates_after : list[Statement] = []) -> list[Gate]:
"""General A-B-A decomposition function for a single gate.

Args:
Expand Down
4 changes: 3 additions & 1 deletion opensquirrel/decomposer/cnot_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ class CNOTDecomposer(Decomposer):

Source of the math: https://threeplusone.com/pubs/on_gates.pdf, chapter 7.5 "ABC decomposition"
"""
def __init__(self,*args,**kwargs):
super(Decomposer,self).__init__(*args,**kwargs)

def decompose(self, g: Gate) -> list[Gate]:
def decompose(self, g: Gate,gates_before : list[Statement] = [], gates_after : list[Statement] = []) -> list[Gate]:
if not isinstance(g, ControlledGate):
# Do nothing:
# - BlochSphereRotation's are only single-qubit,
Expand Down
31 changes: 25 additions & 6 deletions opensquirrel/decomposer/general_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@

class Decomposer(ABC):
@abstractmethod
def decompose(self, gate: Gate) -> list[Gate]:
def decompose(self, gate: Gate,gates_before : list[Statement] = [], gates_after : list[Statement] = []) -> list[Gate]:
raise NotImplementedError()

def uses_multi_gate_replacement(self):
return False

def remove_predecessor_gates(self) -> List[int]: #return relative index or none
return []

def remove_successor_gates(self) -> List[int]: #return relative index or none
return []

def check_gate_replacement(gate: Gate, replacement_gates: Iterable[Gate]) -> None:
gate_qubit_indices = [q.index for q in gate.get_qubit_operands()]
Expand Down Expand Up @@ -47,9 +55,21 @@ def decompose(ir: IR, decomposer: Decomposer) -> None:
continue

gate = statement
replacement_gates: list[Gate] = decomposer.decompose(statement)
check_gate_replacement(gate, replacement_gates)

gates_before = ir.statements[:statement_index]
gates_after = ir.statements[statement_index+1:]
replacement_gates: list[Gate] = decomposer.decompose(statement,gates_before,gates_after)
if decomposer.uses_multi_gate_replacement():
del_idx = [pred_rel_idx+statement_index for pred_rel_idx in decomposer.remove_predecessor_gates()]
del_idx.extend([succ_rel_idx+statement_index for succ_rel_idx in decomposer.remove_successor_gates()])
for index in sorted(del_idx, reverse=True):
del ir.statements[index]
if del_idx:
statement_index = min(del_idx)+1
ir.statements[statement_index : statement_index + 1] = replacement_gates
statement_index += len(replacement_gates)
continue
else:
check_gate_replacement(gate, replacement_gates)
ir.statements[statement_index : statement_index + 1] = replacement_gates
statement_index += len(replacement_gates)

Expand All @@ -59,7 +79,7 @@ def __init__(self, gate_generator: Callable[..., Gate], replacement_function: Ca
self.gate_generator = gate_generator
self.replacement_function = replacement_function

def decompose(self, g: Gate) -> list[Gate]:
def decompose(self, g: Gate,gates_before : list[Statement] = [], gates_after : list[Statement] = []) -> list[Gate]:
if g.is_anonymous or g.generator != self.gate_generator:
return [g]
arguments = () if g.arguments is None else g.arguments
Expand All @@ -69,5 +89,4 @@ def decompose(self, g: Gate) -> list[Gate]:
def replace(ir: IR, gate_generator: Callable[..., Gate], f: Callable[..., list[Gate]]) -> None:
"""Does the same as decomposer, but only applies to a given gate."""
generic_replacer = _GenericReplacer(gate_generator, f)

decompose(ir, generic_replacer)
2 changes: 1 addition & 1 deletion opensquirrel/decomposer/mckay_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


class McKayDecomposer(Decomposer):
def decompose(self, g: Gate) -> list[Gate]:
def decompose(self, g: Gate,gates_before : list[Statement] = [], gates_after : list[Statement] = []) -> list[Gate]:
"""Return the McKay decomposition of a 1-qubit gate as a list of gates.
gate ----> Rz.Rx(pi/2).Rz.Rx(pi/2).Rz

Expand Down
35 changes: 35 additions & 0 deletions opensquirrel/decomposer/twoqubitgate_decomposer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from __future__ import annotations

import math

from opensquirrel.common import ATOL
from opensquirrel.decomposer.aba_decomposer import ZYZDecomposer
from opensquirrel.decomposer.general_decomposer import Decomposer
from opensquirrel.default_gates import H, Z, H, X, GateKind
from opensquirrel.ir import BlochSphereRotation, ControlledGate, Float, Gate
from opensquirrel.merger import general_merger
from opensquirrel.utils.identity_filter import filter_out_identities
from opensquirrel.decomposer.general_decomposer import Decomposer


class TwoQubitGateFolder(Decomposer):
def uses_multi_gate_replacement(self):
return True

def remove_predecessor_gates(self) -> List[int]:
return [-1]

def remove_successor_gates(self) -> List[int]:
return [1]

def decompose(self, g: Gate, gates_before: list[Statement] = [], gates_after: list[Statement] = []) -> list[Gate]:
if g.gatekind is GateKind.Z:
"""
Decomposes 2-qubit H.Z.H to X gate.
"""
try:
if gates_before[-1].gatekind is GateKind.H and gates_after[0].gatekind is GateKind.H:
return [X(g.get_qubit_operands()[0])]
except Exception as _:
pass
return [g]
23 changes: 16 additions & 7 deletions opensquirrel/default_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,27 @@

import numpy as np

from opensquirrel.ir import BlochSphereRotation, ControlledGate, Float, Gate, Int, MatrixGate, Qubit, named_gate
from opensquirrel.ir import BlochSphereRotation, ControlledGate, Float, Gate, Int, MatrixGate, Qubit, named_gate, GateKind


@named_gate
def I(q: Qubit) -> BlochSphereRotation: # noqa: E743
return BlochSphereRotation.identity(q)

bs = BlochSphereRotation.identity(q)
bs.gatekind = GateKind.I
return bs

@named_gate
def H(q: Qubit) -> BlochSphereRotation:
return BlochSphereRotation(qubit=q, axis=(1, 0, 1), angle=math.pi, phase=math.pi / 2)
bs = BlochSphereRotation(qubit=q, axis=(1, 0, 1), angle=math.pi, phase=math.pi / 2)
bs.gatekind = GateKind.H
return bs


@named_gate
def X(q: Qubit) -> BlochSphereRotation:
return BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
bs = BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
bs.gatekind = GateKind.X
return bs


@named_gate
Expand All @@ -35,7 +40,9 @@ def mX90(q: Qubit) -> BlochSphereRotation:

@named_gate
def Y(q: Qubit) -> BlochSphereRotation:
return BlochSphereRotation(qubit=q, axis=(0, 1, 0), angle=math.pi, phase=math.pi / 2)
bs = BlochSphereRotation(qubit=q, axis=(0, 1, 0), angle=math.pi, phase=math.pi / 2)
bs.gatekind = GateKind.Y
return bs


@named_gate
Expand All @@ -50,7 +57,9 @@ def mY90(q: Qubit) -> BlochSphereRotation:

@named_gate
def Z(q: Qubit) -> BlochSphereRotation:
return BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=math.pi, phase=math.pi / 2)
bs = BlochSphereRotation(qubit=q, axis=(0, 0, 1), angle=math.pi, phase=math.pi / 2)
bs.gatekind = GateKind.Z
return bs


@named_gate
Expand Down
9 changes: 9 additions & 0 deletions opensquirrel/ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from collections.abc import Callable, Sequence
from dataclasses import dataclass
from functools import wraps
from enum import Enum
from typing import Any, Union, cast, overload

import numpy as np
Expand Down Expand Up @@ -290,6 +291,13 @@ def accept(self, visitor: IRVisitor) -> Any:
def get_qubit_operands(self) -> list[Qubit]:
return [self.qubit]

class GateKind(Enum):
UNK = 0
I = 1 #PauliI
X = 2 #PauliX
Y = 3 #PauliY
Z = 4 #PauliZ
H = 5 #Hadamard

class Gate(Statement, ABC):
def __init__(
Expand All @@ -302,6 +310,7 @@ def __init__(
# Note: two gates are considered equal even when their generators/arguments are different.
self.generator = generator
self.arguments = arguments
self.gatekind = GateKind.UNK

def __eq__(self, other: object) -> bool:
if not isinstance(other, Gate):
Expand Down
3 changes: 3 additions & 0 deletions opensquirrel/register_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ def from_ast(cls, ast: cqasm.semantic.Program) -> Self:
bit_register = BitRegister.from_ast(ast)
return cls(qubit_register, bit_register)

def __contains__(self, qubit_item:Qubit):
return qubit_item in self.qubit_register

def __eq__(self, other: Any) -> bool:
if not isinstance(other, RegisterManager):
return False
Expand Down
6 changes: 4 additions & 2 deletions test/test_ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from numpy.typing import ArrayLike

from opensquirrel.common import ATOL
from opensquirrel.ir import Axis, Bit, BlochSphereRotation, ControlledGate, Expression, MatrixGate, Measure, Qubit
from opensquirrel.ir import Axis, Bit, BlochSphereRotation, ControlledGate, Expression, MatrixGate, Measure, Qubit, GateKind


class TestAxis:
Expand Down Expand Up @@ -269,7 +269,9 @@ def test_get_qubit_operands(self, gate: MatrixGate) -> None:
assert gate.get_qubit_operands() == [Qubit(42), Qubit(100)]

def test_is_identity(self, gate: MatrixGate) -> None:
assert MatrixGate(np.eye(4, dtype=np.complex128), operands=[Qubit(42), Qubit(100)]).is_identity()
ms = MatrixGate(np.eye(4, dtype=np.complex128), operands=[Qubit(42), Qubit(100)])
assert ms.gatekind == GateKind.UNK
assert ms.is_identity()
assert not gate.is_identity()

def test_matrix_gate_same_control_and_target_qubit(self) -> None:
Expand Down
15 changes: 14 additions & 1 deletion test/test_replacer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,22 @@

from opensquirrel import CircuitBuilder
from opensquirrel.decomposer.general_decomposer import Decomposer, check_gate_replacement, decompose, replace
from opensquirrel.decomposer.twoqubitgate_decomposer import TwoQubitGateFolder
from opensquirrel.default_gates import CNOT, Y90, H, I, Ry, Rz, X, Z, sqrtSWAP
from opensquirrel.ir import BlochSphereRotation, Float, Gate, Qubit

class TestCheckTwoQubitDecomposer:
"""check if HZH gate can be folded to X
H(Qubit(0)),Z(Qubit(0)),H(Qubit(0))) -> X(Qubit(0)))
"""
def test_HZH_to_X(self):
builder1 = CircuitBuilder(3)
builder1.H(Qubit(0))
builder1.Z(Qubit(0))
builder1.H(Qubit(0))
circuit = builder1.to_circuit()
decompose(circuit.ir,decomposer=TwoQubitGateFolder())
assert(len(circuit.ir.statements) == 1)

class TestCheckGateReplacement:
@pytest.mark.parametrize(
Expand Down Expand Up @@ -114,7 +127,7 @@ def test_replace_generic(self) -> None:

# A simple decomposer function that adds identities before and after single-qubit gates.
class TestDecomposer(Decomposer):
def decompose(self, g: Gate) -> list[Gate]:
def decompose(self, g: Gate,gates_before : list[Statement] = [], gates_after : list[Statement] = []) -> list[Gate]:
if isinstance(g, BlochSphereRotation):
return [I(g.qubit), g, I(g.qubit)]
return [g]
Expand Down
Loading