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
  • Loading branch information
Juan Boschero committed Mar 6, 2024
1 parent 2c049f4 commit 2b68d1f
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 31 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
43 changes: 27 additions & 16 deletions opensquirrel/squirrel_ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@
from dataclasses import dataclass
from functools import wraps
from math import cos, sin
from typing import Callable, List, Optional, Tuple
from typing import Callable, List, Optional, Tuple, Union

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 2b68d1f

Please sign in to comment.