Skip to content

Commit eaf1334

Browse files
cgogolindamiansteiger
authored andcommitted
Implement MatrixGate and simplify __eq__ in BasicGate to improve performance (#288)
1 parent ef99f48 commit eaf1334

File tree

8 files changed

+150
-76
lines changed

8 files changed

+150
-76
lines changed

docs/projectq.ops.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The operations collection consists of various default gates and is a work-in-pro
66
.. autosummary::
77

88
projectq.ops.BasicGate
9+
projectq.ops.MatrixGate
910
projectq.ops.SelfInverseGate
1011
projectq.ops.BasicRotationGate
1112
projectq.ops.BasicPhaseGate

projectq/backends/_sim/_simulator_test.py

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
from projectq.cengines import (BasicEngine, BasicMapperEngine, DummyEngine,
3131
LocalOptimizer, NotYetMeasuredError)
3232
from projectq.ops import (All, Allocate, BasicGate, BasicMathGate, CNOT,
33-
Command, H, Measure, QubitOperator, Rx, Ry, Rz, S,
34-
TimeEvolution, Toffoli, X, Y, Z)
33+
Command, H, MatrixGate, Measure, QubitOperator,
34+
Rx, Ry, Rz, S, TimeEvolution, Toffoli, X, Y, Z)
3535
from projectq.meta import Control, Dagger, LogicalQubitIDTag
3636
from projectq.types import WeakQubitRef
3737

@@ -97,37 +97,32 @@ def receive(self, command_list):
9797
return None
9898

9999

100-
class Mock1QubitGate(BasicGate):
101-
def __init__(self):
102-
BasicGate.__init__(self)
103-
self.cnt = 0
100+
class Mock1QubitGate(MatrixGate):
101+
def __init__(self):
102+
MatrixGate.__init__(self)
103+
self.cnt = 0
104104

105-
@property
106-
def matrix(self):
107-
self.cnt += 1
108-
return [[0, 1], [1, 0]]
105+
@property
106+
def matrix(self):
107+
self.cnt += 1
108+
return [[0, 1], [1, 0]]
109109

110110

111-
class Mock6QubitGate(BasicGate):
112-
def __init__(self):
113-
BasicGate.__init__(self)
114-
self.cnt = 0
111+
class Mock6QubitGate(MatrixGate):
112+
def __init__(self):
113+
MatrixGate.__init__(self)
114+
self.cnt = 0
115115

116-
@property
117-
def matrix(self):
118-
self.cnt += 1
119-
return numpy.eye(2 ** 6)
116+
@property
117+
def matrix(self):
118+
self.cnt += 1
119+
return numpy.eye(2 ** 6)
120120

121121

122122
class MockNoMatrixGate(BasicGate):
123-
def __init__(self):
124-
BasicGate.__init__(self)
125-
self.cnt = 0
126-
127-
@property
128-
def matrix(self):
129-
self.cnt += 1
130-
raise AttributeError
123+
def __init__(self):
124+
BasicGate.__init__(self)
125+
self.cnt = 0
131126

132127

133128
def test_simulator_is_available(sim):
@@ -147,15 +142,15 @@ def test_simulator_is_available(sim):
147142

148143
new_cmd.gate = Mock1QubitGate()
149144
assert sim.is_available(new_cmd)
150-
assert new_cmd.gate.cnt == 4
145+
assert new_cmd.gate.cnt == 1
151146

152147
new_cmd.gate = Mock6QubitGate()
153148
assert not sim.is_available(new_cmd)
154-
assert new_cmd.gate.cnt == 4
149+
assert new_cmd.gate.cnt == 1
155150

156151
new_cmd.gate = MockNoMatrixGate()
157152
assert not sim.is_available(new_cmd)
158-
assert new_cmd.gate.cnt == 7
153+
assert new_cmd.gate.cnt == 0
159154

160155

161156
def test_simulator_cheat(sim):

projectq/ops/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from ._basics import (NotMergeable,
1616
NotInvertible,
1717
BasicGate,
18+
MatrixGate,
1819
SelfInverseGate,
1920
BasicRotationGate,
2021
ClassicalInstructionGate,

projectq/ops/_basics.py

Lines changed: 78 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class NotInvertible(Exception):
6464

6565
class BasicGate(object):
6666
"""
67-
Base class of all gates.
67+
Base class of all gates. (Don't use it directly but derive from it)
6868
"""
6969
def __init__(self):
7070
"""
@@ -204,39 +204,19 @@ def __or__(self, qubits):
204204
apply_command(cmd)
205205

206206
def __eq__(self, other):
207-
""" Return True if equal (i.e., instance of same class).
208-
209-
Unless both have a matrix attribute in which case we also check
210-
that the matrices are identical as people might want to do the
211-
following:
212-
213-
Example:
214-
.. code-block:: python
215-
216-
gate = BasicGate()
217-
gate.matrix = numpy.matrix([[1,0],[0, -1]])
218-
"""
219-
if hasattr(self, 'matrix'):
220-
if not hasattr(other, 'matrix'):
221-
return False
222-
if hasattr(other, 'matrix'):
223-
if not hasattr(self, 'matrix'):
224-
return False
225-
if hasattr(self, 'matrix') and hasattr(other, 'matrix'):
226-
if (not isinstance(self.matrix, np.matrix) or
227-
not isinstance(other.matrix, np.matrix)):
228-
raise TypeError("One of the gates doesn't have the correct "
229-
"type (numpy.matrix) for the matrix "
230-
"attribute.")
231-
if (self.matrix.shape == other.matrix.shape and
232-
np.allclose(self.matrix, other.matrix,
233-
rtol=RTOL, atol=ATOL,
234-
equal_nan=False)):
235-
return True
236-
else:
237-
return False
207+
"""
208+
Equality comparision
209+
210+
Return True if instance of the same class, unless other is an instance
211+
of :class:MatrixGate, in which case equality is to be checked by testing
212+
for existence and (approximate) equality of matrix representations.
213+
"""
214+
if isinstance(other, self.__class__):
215+
return True
216+
elif isinstance(other, MatrixGate):
217+
return NotImplemented
238218
else:
239-
return isinstance(other, self.__class__)
219+
return False
240220

241221
def __ne__(self, other):
242222
return not self.__eq__(other)
@@ -248,6 +228,71 @@ def __hash__(self):
248228
return hash(str(self))
249229

250230

231+
class MatrixGate(BasicGate):
232+
"""
233+
Defines a gate class whose instances are defined by a matrix.
234+
235+
Note:
236+
Use this gate class only for gates acting on a small numbers of qubits.
237+
In general, consider instead using one of the provided ProjectQ gates
238+
or define a new class as this allows the compiler to work symbolically.
239+
240+
Example:
241+
242+
.. code-block:: python
243+
244+
gate = MatrixGate([[0, 1], [1, 0]])
245+
gate | qubit
246+
"""
247+
def __init__(self, matrix=None):
248+
"""
249+
Initialize MatrixGate
250+
251+
Args:
252+
matrix(numpy.matrix): matrix which defines the gate. Default: None
253+
"""
254+
BasicGate.__init__(self)
255+
self._matrix = np.matrix(matrix) if matrix is not None else None
256+
257+
@property
258+
def matrix(self):
259+
return self._matrix
260+
261+
@matrix.setter
262+
def matrix(self, matrix):
263+
self._matrix = np.matrix(matrix)
264+
265+
def __eq__(self, other):
266+
"""
267+
Equality comparision
268+
269+
Return True only if both gates have a matrix respresentation and the
270+
matrices are (approximately) equal. Otherwise return False.
271+
"""
272+
if not hasattr(other, 'matrix'):
273+
return False
274+
if (not isinstance(self.matrix, np.matrix) or
275+
not isinstance(other.matrix, np.matrix)):
276+
raise TypeError("One of the gates doesn't have the correct "
277+
"type (numpy.matrix) for the matrix "
278+
"attribute.")
279+
if (self.matrix.shape == other.matrix.shape and
280+
np.allclose(self.matrix, other.matrix,
281+
rtol=RTOL, atol=ATOL,
282+
equal_nan=False)):
283+
return True
284+
return False
285+
286+
def __str__(self):
287+
return("MatrixGate(" + str(self.matrix.tolist()) + ")")
288+
289+
def __hash__(self):
290+
return hash(str(self))
291+
292+
def get_inverse(self):
293+
return MatrixGate(np.linalg.inv(self.matrix))
294+
295+
251296
class SelfInverseGate(BasicGate):
252297
"""
253298
Self-inverse basic gate class.

projectq/ops/_basics_test.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import pytest
2222

2323
from projectq.types import Qubit, Qureg
24-
from projectq.ops import Command
24+
from projectq.ops import Command, X
2525
from projectq import MainEngine
2626
from projectq.cengines import DummyEngine
2727

@@ -118,13 +118,12 @@ def test_basic_gate_compare():
118118
gate2 = _basics.BasicGate()
119119
assert gate1 == gate2
120120
assert not (gate1 != gate2)
121-
gate3 = _basics.BasicGate()
121+
gate3 = _basics.MatrixGate()
122122
gate3.matrix = np.matrix([[1, 0], [0, -1]])
123123
assert gate1 != gate3
124-
gate4 = _basics.BasicGate()
124+
gate4 = _basics.MatrixGate()
125125
gate4.matrix = [[1, 0], [0, -1]]
126-
with pytest.raises(TypeError):
127-
gate4 == gate3
126+
assert gate4 == gate3
128127

129128

130129
def test_comparing_different_gates():
@@ -295,3 +294,35 @@ def __init__(self):
295294
# Test a=2, b=3, and c=5 should give a=2, b=3, c=11
296295
math_fun = gate.get_math_function(("qreg1", "qreg2", "qreg3"))
297296
assert math_fun([2, 3, 5]) == [2, 3, 11]
297+
298+
299+
def test_matrix_gate():
300+
gate1 = _basics.MatrixGate()
301+
gate2 = _basics.MatrixGate()
302+
with pytest.raises(TypeError):
303+
assert gate1 == gate2
304+
gate3 = _basics.MatrixGate([[0, 1], [1, 0]])
305+
gate4 = _basics.MatrixGate([[0, 1], [1, 0]])
306+
gate5 = _basics.MatrixGate([[1, 0], [0, -1]])
307+
assert gate3 == gate4
308+
assert gate4 != gate5
309+
with pytest.raises(TypeError):
310+
assert gate1 != gate3
311+
with pytest.raises(TypeError):
312+
assert gate3 != gate1
313+
gate6 = _basics.BasicGate()
314+
assert gate6 != gate1
315+
assert gate6 != gate3
316+
assert gate1 != gate6
317+
assert gate3 != gate6
318+
gate7 = gate5.get_inverse()
319+
gate8 = _basics.MatrixGate([[1, 0], [0, (1+1j)/math.sqrt(2)]])
320+
assert gate7 == gate5
321+
assert gate7 != gate8
322+
gate9 = _basics.MatrixGate([[1, 0], [0, (1-1j)/math.sqrt(2)]])
323+
gate10 = gate9.get_inverse()
324+
assert gate10 == gate8
325+
assert gate3 == X
326+
assert X == gate3
327+
assert str(gate3) == "MatrixGate([[0, 1], [1, 0]])"
328+
assert hash(gate3) == hash("MatrixGate([[0, 1], [1, 0]])")

projectq/ops/_gates.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
from projectq.ops import get_inverse
3939
from ._basics import (BasicGate,
40+
MatrixGate,
4041
SelfInverseGate,
4142
BasicRotationGate,
4243
BasicPhaseGate,

projectq/setups/decompositions/arb1qubit2rzandry_test.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
from projectq.backends import Simulator
2424
from projectq.cengines import (AutoReplacer, DecompositionRuleSet,
2525
DummyEngine, InstructionFilter, MainEngine)
26-
from projectq.ops import (BasicGate, ClassicalInstructionGate, Measure, Ph, R,
27-
Rx, Ry, Rz, X)
26+
from projectq.ops import (BasicGate, ClassicalInstructionGate, MatrixGate,
27+
Measure, Ph, R, Rx, Ry, Rz, X)
2828
from projectq.meta import Control
2929

3030
from . import arb1qubit2rzandry as arb1q
@@ -51,7 +51,7 @@ def test_recognize_incorrect_gates():
5151
# Does not have matrix attribute:
5252
BasicGate() | qubit
5353
# Two qubit gate:
54-
two_qubit_gate = BasicGate()
54+
two_qubit_gate = MatrixGate()
5555
two_qubit_gate.matrix = [[1, 0, 0, 0], [0, 1, 0, 0],
5656
[0, 0, 1, 0], [0, 0, 0, 1]]
5757
two_qubit_gate | qubit
@@ -121,7 +121,7 @@ def create_test_matrices():
121121
def test_decomposition(gate_matrix):
122122
for basis_state in ([1, 0], [0, 1]):
123123
# Create single qubit gate with gate_matrix
124-
test_gate = BasicGate()
124+
test_gate = MatrixGate()
125125
test_gate.matrix = np.matrix(gate_matrix)
126126

127127
correct_dummy_eng = DummyEngine(save_commands=True)
@@ -165,7 +165,7 @@ def test_decomposition(gate_matrix):
165165
[[0, 2], [4, 0]],
166166
[[1, 2], [4, 0]]])
167167
def test_decomposition_errors(gate_matrix):
168-
test_gate = BasicGate()
168+
test_gate = MatrixGate()
169169
test_gate.matrix = np.matrix(gate_matrix)
170170
rule_set = DecompositionRuleSet(modules=[arb1q])
171171
eng = MainEngine(backend=DummyEngine(),

projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
from projectq.cengines import (AutoReplacer, DecompositionRuleSet,
2222
DummyEngine, InstructionFilter, MainEngine)
2323
from projectq.meta import Control
24-
from projectq.ops import (All, BasicGate, ClassicalInstructionGate, Measure,
25-
Ph, R, Rx, Ry, Rz, X, XGate)
24+
from projectq.ops import (All, BasicGate, ClassicalInstructionGate,
25+
MatrixGate, Measure, Ph, R, Rx, Ry, Rz, X, XGate)
2626
from projectq.setups.decompositions import arb1qubit2rzandry_test as arb1q_t
2727

2828
from . import carb1qubit2cnotrzandry as carb1q
@@ -57,7 +57,7 @@ def test_recognize_incorrect_gates():
5757
# Does not have matrix attribute:
5858
BasicGate() | qubit
5959
# Two qubit gate:
60-
two_qubit_gate = BasicGate()
60+
two_qubit_gate = MatrixGate()
6161
two_qubit_gate.matrix = np.matrix([[1, 0, 0, 0], [0, 1, 0, 0],
6262
[0, 0, 1, 0], [0, 0, 0, 1]])
6363
two_qubit_gate | qubit
@@ -94,7 +94,7 @@ def test_recognize_v(gate_matrix):
9494
@pytest.mark.parametrize("gate_matrix", arb1q_t.create_test_matrices())
9595
def test_decomposition(gate_matrix):
9696
# Create single qubit gate with gate_matrix
97-
test_gate = BasicGate()
97+
test_gate = MatrixGate()
9898
test_gate.matrix = np.matrix(gate_matrix)
9999

100100
for basis_state in ([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0],

0 commit comments

Comments
 (0)