Skip to content

Commit 2b68d1f

Browse files
committed
Add check for hybrid Control/Matrix Gate classes + test
- 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
1 parent 2c049f4 commit 2b68d1f

File tree

6 files changed

+150
-31
lines changed

6 files changed

+150
-31
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ dmypy.json
153153
cython_debug/
154154

155155
# PyCharm
156+
.idea/
156157
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157158
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158159
# and can be added to the global gitignore or merged into this file. For a more nuclear

opensquirrel/replacer.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from abc import ABC, abstractmethod
22
from typing import Callable, List
33

4-
from opensquirrel.circuit_matrix_calculator import get_circuit_matrix
54
from opensquirrel.common import are_matrices_equivalent_up_to_global_phase
65
from opensquirrel.squirrel_ir import (
76
BlochSphereRotation,
@@ -43,17 +42,6 @@ def visit_controlled_gate(self, controlled_gate: ControlledGate):
4342
return result
4443

4544

46-
def get_replacement_matrix(replacement: List[Gate], qubit_mappings):
47-
replacement_ir = SquirrelIR(number_of_qubits=len(qubit_mappings), qubit_register_name="q_temp")
48-
qubit_remapper = _QubitReIndexer(qubit_mappings)
49-
50-
for gate in replacement:
51-
gate_with_remapped_qubits = gate.accept(qubit_remapper)
52-
replacement_ir.add_gate(gate_with_remapped_qubits)
53-
54-
return get_circuit_matrix(replacement_ir)
55-
56-
5745
def check_valid_replacement(statement, replacement):
5846
expected_qubit_operands = statement.get_qubit_operands()
5947
replacement_qubit_operands = set()
@@ -62,8 +50,10 @@ def check_valid_replacement(statement, replacement):
6250
if set(expected_qubit_operands) != replacement_qubit_operands:
6351
raise Exception(f"Replacement for gate {statement.name} does not seem to operate on the right qubits")
6452

65-
replacement_matrix = get_replacement_matrix(replacement, expected_qubit_operands)
66-
replaced_matrix = get_replacement_matrix([statement], expected_qubit_operands)
53+
from opensquirrel.utils.matrix_expander import get_matrix_after_qubit_remapping
54+
55+
replacement_matrix = get_matrix_after_qubit_remapping(replacement, expected_qubit_operands)
56+
replaced_matrix = get_matrix_after_qubit_remapping([statement], expected_qubit_operands)
6757

6858
if not are_matrices_equivalent_up_to_global_phase(replacement_matrix, replaced_matrix):
6959
raise Exception(f"Replacement for gate {statement.name} does not preserve the quantum state")

opensquirrel/squirrel_ir.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@
44
from dataclasses import dataclass
55
from functools import wraps
66
from math import cos, sin
7-
from typing import Callable, List, Optional, Tuple
7+
from typing import Callable, List, Optional, Tuple, Union
88

99
import numpy as np
1010

11-
from opensquirrel.common import ATOL, X, Y, Z, normalize_angle, normalize_axis
11+
from opensquirrel.common import (
12+
ATOL,
13+
X,
14+
Y,
15+
Z,
16+
are_matrices_equivalent_up_to_global_phase,
17+
normalize_angle,
18+
normalize_axis,
19+
)
1220

1321

1422
class SquirrelIRVisitor(ABC):
@@ -90,6 +98,11 @@ def __init__(self, generator, arguments):
9098
self.generator = generator
9199
self.arguments = arguments
92100

101+
def __eq__(self, other):
102+
if not isinstance(other, Gate):
103+
return False
104+
return _compare_gate_classes(self, other)
105+
93106
@property
94107
def name(self) -> Optional[str]:
95108
return self.generator.__name__ if self.generator else "<anonymous>"
@@ -171,12 +184,6 @@ def __init__(self, matrix: np.ndarray, operands: List[Qubit], generator=None, ar
171184
self.matrix = matrix
172185
self.operands = operands
173186

174-
def __eq__(self, other):
175-
# TODO: Determine whether we shall allow for a global phase difference here.
176-
if not isinstance(other, MatrixGate):
177-
return False # FIXME: a MatrixGate can hide a ControlledGate. https://github.com/QuTech-Delft/OpenSquirrel/issues/88
178-
return np.allclose(self.matrix, other.matrix)
179-
180187
def __repr__(self):
181188
return f"MatrixGate(qubits={self.operands}, matrix={self.matrix})"
182189

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

202-
def __eq__(self, other):
203-
if not isinstance(other, ControlledGate):
204-
return False # FIXME: a MatrixGate can hide a ControlledGate. https://github.com/QuTech-Delft/OpenSquirrel/issues/88
205-
if self.control_qubit != other.control_qubit:
206-
return False
207-
208-
return self.target_gate == other.target_gate
209-
210209
def __repr__(self):
211210
return f"ControlledGate(control_qubit={self.control_qubit}, {self.target_gate})"
212211

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

223222

223+
def _compare_gate_classes(g1: Gate, g2: Gate) -> bool:
224+
225+
union_mapping = list(set(g1.get_qubit_operands()) | set(g2.get_qubit_operands()))
226+
227+
from opensquirrel.utils.matrix_expander import get_matrix_after_qubit_remapping
228+
229+
matrix_g1 = get_matrix_after_qubit_remapping([g1], union_mapping)
230+
matrix_g2 = get_matrix_after_qubit_remapping([g2], union_mapping)
231+
232+
return are_matrices_equivalent_up_to_global_phase(matrix_g1, matrix_g2)
233+
234+
224235
def named_gate(gate_generator: Callable[..., Gate]) -> Callable[..., Gate]:
225236
@wraps(gate_generator)
226237
def wrapper(*args, **kwargs):

opensquirrel/utils/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from opensquirrel.utils.matrix_expander import get_matrix, get_matrix_after_qubit_remapping
2+
3+
__all__ = ["get_matrix", "get_matrix_after_qubit_remapping"]

opensquirrel/utils/matrix_expander.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import numpy as np
55

66
from opensquirrel.common import can1
7-
from opensquirrel.squirrel_ir import BlochSphereRotation, ControlledGate, Gate, Qubit, SquirrelIRVisitor
7+
from opensquirrel.replacer import _QubitReIndexer
8+
from opensquirrel.squirrel_ir import BlochSphereRotation, ControlledGate, Gate, Qubit, SquirrelIR, SquirrelIRVisitor
89

910

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

184185
expander = MatrixExpander(number_of_qubits)
185186
return gate.accept(expander)
187+
188+
189+
def get_matrix_after_qubit_remapping(replacement: List[Gate], qubit_mappings: List[Qubit]):
190+
from opensquirrel.circuit_matrix_calculator import get_circuit_matrix
191+
192+
replacement_ir = SquirrelIR(number_of_qubits=len(qubit_mappings), qubit_register_name="q_temp")
193+
qubit_remapper = _QubitReIndexer(qubit_mappings)
194+
for gate in replacement:
195+
gate_with_remapped_qubits = gate.accept(qubit_remapper)
196+
replacement_ir.add_gate(gate_with_remapped_qubits)
197+
198+
return get_circuit_matrix(replacement_ir)

test/test_squirrel_ir.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import unittest
2+
3+
import numpy as np
4+
5+
from opensquirrel.default_gates import *
6+
7+
8+
class SquirrelIRTest(unittest.TestCase):
9+
def test_cnot_equality(self):
10+
cnot_matrix_gate = MatrixGate(
11+
np.array(
12+
[
13+
[1, 0, 0, 0],
14+
[0, 1, 0, 0],
15+
[0, 0, 0, 1],
16+
[0, 0, 1, 0],
17+
]
18+
),
19+
operands=[Qubit(4), Qubit(100)],
20+
)
21+
22+
cnot_controlled_gate = ControlledGate(
23+
Qubit(4), BlochSphereRotation(qubit=Qubit(100), axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
24+
)
25+
26+
self.assertTrue(cnot_controlled_gate == cnot_matrix_gate)
27+
28+
def test_cnot_inequality(self):
29+
swap_matrix_gate = MatrixGate(
30+
np.array(
31+
[
32+
[1, 0, 0, 0],
33+
[0, 0, 1, 0],
34+
[0, 1, 0, 0],
35+
[0, 0, 0, 1],
36+
]
37+
),
38+
operands=[Qubit(4), Qubit(100)],
39+
)
40+
41+
cnot_controlled_gate = ControlledGate(
42+
Qubit(4), BlochSphereRotation(qubit=Qubit(100), axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
43+
)
44+
45+
self.assertFalse(cnot_controlled_gate == swap_matrix_gate)
46+
47+
def test_different_qubits_gate(self):
48+
large_identity_matrix_gate = MatrixGate(
49+
np.array(
50+
[
51+
[1, 0, 0, 0],
52+
[0, 1, 0, 0],
53+
[0, 0, 1, 0],
54+
[0, 0, 0, 1],
55+
]
56+
),
57+
operands=[Qubit(0), Qubit(2)],
58+
)
59+
small_identity_control_gate = ControlledGate(
60+
Qubit(4), BlochSphereRotation(qubit=Qubit(2), axis=(1, 0, 0), angle=0, phase=0)
61+
)
62+
63+
self.assertTrue(large_identity_matrix_gate == small_identity_control_gate)
64+
65+
def test_inverse_gate(self):
66+
inverted_matrix_gate = MatrixGate(
67+
np.array(
68+
[
69+
[1, 0, 0, 0],
70+
[0, 0, 0, 1],
71+
[0, 0, 1, 0],
72+
[0, 1, 0, 0],
73+
]
74+
),
75+
operands=[Qubit(0), Qubit(1)],
76+
)
77+
78+
inverted_cnot_gate = ControlledGate(
79+
Qubit(1), BlochSphereRotation(qubit=Qubit(0), axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
80+
)
81+
82+
self.assertTrue(inverted_matrix_gate == inverted_cnot_gate)
83+
84+
def test_global_phase(self):
85+
inverted_matrix_with_phase = MatrixGate(
86+
np.array(
87+
[
88+
[1j, 0, 0, 0],
89+
[0, 0, 0, 1j],
90+
[0, 0, 1j, 0],
91+
[0, 1j, 0, 0],
92+
]
93+
),
94+
operands=[Qubit(0), Qubit(1)],
95+
)
96+
97+
inverted_cnot_gate = ControlledGate(
98+
Qubit(1), BlochSphereRotation(qubit=Qubit(0), axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
99+
)
100+
101+
self.assertTrue(inverted_matrix_with_phase == inverted_cnot_gate)

0 commit comments

Comments
 (0)