Skip to content

Commit

Permalink
Add check for hybrid Control/Matrix Gate classes + test
Browse files Browse the repository at this point in the history
- Configured equivalence of gates from subclass to base class
- Renamed get_matrix to get_matrix_after_qubit_remapping and moved function to matrix_expander
- Added tests to check equivalence between MatrixGate and ControlGate objects

removed union import from squirrel_ir
  • Loading branch information
Juan Boschero committed Mar 8, 2024
1 parent 76068f5 commit fa0592c
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 30 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ dmypy.json
cython_debug/

# PyCharm
.idea/
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
Expand Down
18 changes: 4 additions & 14 deletions opensquirrel/replacer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from abc import ABC, abstractmethod
from typing import Callable, List

from opensquirrel.circuit_matrix_calculator import get_circuit_matrix
from opensquirrel.common import are_matrices_equivalent_up_to_global_phase
from opensquirrel.squirrel_ir import (
BlochSphereRotation,
Expand Down Expand Up @@ -43,17 +42,6 @@ def visit_controlled_gate(self, controlled_gate: ControlledGate):
return result


def get_replacement_matrix(replacement: List[Gate], qubit_mappings):
replacement_ir = SquirrelIR(number_of_qubits=len(qubit_mappings), qubit_register_name="q_temp")
qubit_remapper = _QubitReIndexer(qubit_mappings)

for gate in replacement:
gate_with_remapped_qubits = gate.accept(qubit_remapper)
replacement_ir.add_gate(gate_with_remapped_qubits)

return get_circuit_matrix(replacement_ir)


def check_valid_replacement(statement, replacement):
expected_qubit_operands = statement.get_qubit_operands()
replacement_qubit_operands = set()
Expand All @@ -62,8 +50,10 @@ def check_valid_replacement(statement, replacement):
if set(expected_qubit_operands) != replacement_qubit_operands:
raise Exception(f"Replacement for gate {statement.name} does not seem to operate on the right qubits")

replacement_matrix = get_replacement_matrix(replacement, expected_qubit_operands)
replaced_matrix = get_replacement_matrix([statement], expected_qubit_operands)
from opensquirrel.utils.matrix_expander import get_matrix_after_qubit_remapping

replacement_matrix = get_matrix_after_qubit_remapping(replacement, expected_qubit_operands)
replaced_matrix = get_matrix_after_qubit_remapping([statement], expected_qubit_operands)

if not are_matrices_equivalent_up_to_global_phase(replacement_matrix, replaced_matrix):
raise Exception(f"Replacement for gate {statement.name} does not preserve the quantum state")
Expand Down
41 changes: 26 additions & 15 deletions opensquirrel/squirrel_ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@

import numpy as np

from opensquirrel.common import ATOL, X, Y, Z, normalize_angle, normalize_axis
from opensquirrel.common import (
ATOL,
X,
Y,
Z,
are_matrices_equivalent_up_to_global_phase,
normalize_angle,
normalize_axis,
)


class SquirrelIRVisitor(ABC):
Expand Down Expand Up @@ -90,6 +98,11 @@ def __init__(self, generator, arguments):
self.generator = generator
self.arguments = arguments

def __eq__(self, other):
if not isinstance(other, Gate):
return False
return _compare_gate_classes(self, other)

@property
def name(self) -> Optional[str]:
return self.generator.__name__ if self.generator else "<anonymous>"
Expand Down Expand Up @@ -171,12 +184,6 @@ def __init__(self, matrix: np.ndarray, operands: List[Qubit], generator=None, ar
self.matrix = matrix
self.operands = operands

def __eq__(self, other):
# TODO: Determine whether we shall allow for a global phase difference here.
if not isinstance(other, MatrixGate):
return False # FIXME: a MatrixGate can hide a ControlledGate. https://github.com/QuTech-Delft/OpenSquirrel/issues/88
return np.allclose(self.matrix, other.matrix)

def __repr__(self):
return f"MatrixGate(qubits={self.operands}, matrix={self.matrix})"

Expand All @@ -199,14 +206,6 @@ def __init__(self, control_qubit: Qubit, target_gate: Gate, generator=None, argu
self.control_qubit = control_qubit
self.target_gate = target_gate

def __eq__(self, other):
if not isinstance(other, ControlledGate):
return False # FIXME: a MatrixGate can hide a ControlledGate. https://github.com/QuTech-Delft/OpenSquirrel/issues/88
if self.control_qubit != other.control_qubit:
return False

return self.target_gate == other.target_gate

def __repr__(self):
return f"ControlledGate(control_qubit={self.control_qubit}, {self.target_gate})"

Expand All @@ -221,6 +220,18 @@ def is_identity(self) -> bool:
return self.target_gate.is_identity()


def _compare_gate_classes(g1: Gate, g2: Gate) -> bool:

union_mapping = list(set(g1.get_qubit_operands()) | set(g2.get_qubit_operands()))

from opensquirrel.utils.matrix_expander import get_matrix_after_qubit_remapping

matrix_g1 = get_matrix_after_qubit_remapping([g1], union_mapping)
matrix_g2 = get_matrix_after_qubit_remapping([g2], union_mapping)

return are_matrices_equivalent_up_to_global_phase(matrix_g1, matrix_g2)


def named_gate(gate_generator: Callable[..., Gate]) -> Callable[..., Gate]:
@wraps(gate_generator)
def wrapper(*args, **kwargs):
Expand Down
3 changes: 3 additions & 0 deletions opensquirrel/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from opensquirrel.utils.matrix_expander import get_matrix, get_matrix_after_qubit_remapping

__all__ = ["get_matrix", "get_matrix_after_qubit_remapping"]
15 changes: 14 additions & 1 deletion opensquirrel/utils/matrix_expander.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import numpy as np

from opensquirrel.common import can1
from opensquirrel.squirrel_ir import BlochSphereRotation, ControlledGate, Gate, Qubit, SquirrelIRVisitor
from opensquirrel.replacer import _QubitReIndexer
from opensquirrel.squirrel_ir import BlochSphereRotation, ControlledGate, Gate, Qubit, SquirrelIR, SquirrelIRVisitor


def get_reduced_ket(ket: int, qubits: List[Qubit]) -> int:
Expand Down Expand Up @@ -183,3 +184,15 @@ def get_matrix(gate: Gate, number_of_qubits: int) -> np.ndarray:

expander = MatrixExpander(number_of_qubits)
return gate.accept(expander)


def get_matrix_after_qubit_remapping(replacement: List[Gate], qubit_mappings: List[Qubit]):
from opensquirrel.circuit_matrix_calculator import get_circuit_matrix

replacement_ir = SquirrelIR(number_of_qubits=len(qubit_mappings), qubit_register_name="q_temp")
qubit_remapper = _QubitReIndexer(qubit_mappings)
for gate in replacement:
gate_with_remapped_qubits = gate.accept(qubit_remapper)
replacement_ir.add_gate(gate_with_remapped_qubits)

return get_circuit_matrix(replacement_ir)
101 changes: 101 additions & 0 deletions test/test_squirrel_ir.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import unittest

import numpy as np

from opensquirrel.default_gates import *


class SquirrelIRTest(unittest.TestCase):
def test_cnot_equality(self):
cnot_matrix_gate = MatrixGate(
np.array(
[
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 0],
]
),
operands=[Qubit(4), Qubit(100)],
)

cnot_controlled_gate = ControlledGate(
Qubit(4), BlochSphereRotation(qubit=Qubit(100), axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
)

self.assertTrue(cnot_controlled_gate == cnot_matrix_gate)

def test_cnot_inequality(self):
swap_matrix_gate = MatrixGate(
np.array(
[
[1, 0, 0, 0],
[0, 0, 1, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
]
),
operands=[Qubit(4), Qubit(100)],
)

cnot_controlled_gate = ControlledGate(
Qubit(4), BlochSphereRotation(qubit=Qubit(100), axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
)

self.assertFalse(cnot_controlled_gate == swap_matrix_gate)

def test_different_qubits_gate(self):
large_identity_matrix_gate = MatrixGate(
np.array(
[
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
]
),
operands=[Qubit(0), Qubit(2)],
)
small_identity_control_gate = ControlledGate(
Qubit(4), BlochSphereRotation(qubit=Qubit(2), axis=(1, 0, 0), angle=0, phase=0)
)

self.assertTrue(large_identity_matrix_gate == small_identity_control_gate)

def test_inverse_gate(self):
inverted_matrix_gate = MatrixGate(
np.array(
[
[1, 0, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 0],
[0, 1, 0, 0],
]
),
operands=[Qubit(0), Qubit(1)],
)

inverted_cnot_gate = ControlledGate(
Qubit(1), BlochSphereRotation(qubit=Qubit(0), axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
)

self.assertTrue(inverted_matrix_gate == inverted_cnot_gate)

def test_global_phase(self):
inverted_matrix_with_phase = MatrixGate(
np.array(
[
[1j, 0, 0, 0],
[0, 0, 0, 1j],
[0, 0, 1j, 0],
[0, 1j, 0, 0],
]
),
operands=[Qubit(0), Qubit(1)],
)

inverted_cnot_gate = ControlledGate(
Qubit(1), BlochSphereRotation(qubit=Qubit(0), axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
)

self.assertTrue(inverted_matrix_with_phase == inverted_cnot_gate)

0 comments on commit fa0592c

Please sign in to comment.