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

Added check for hybrid Control/Matrix Gate classes + test #106

Merged
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
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/
juanboschero marked this conversation as resolved.
Show resolved Hide resolved
# 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

juanboschero marked this conversation as resolved.
Show resolved Hide resolved
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.
juanboschero marked this conversation as resolved.
Show resolved Hide resolved
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
juanboschero marked this conversation as resolved.
Show resolved Hide resolved

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
juanboschero marked this conversation as resolved.
Show resolved Hide resolved

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)
1 change: 1 addition & 0 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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):
juanboschero marked this conversation as resolved.
Show resolved Hide resolved
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)