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

Implement MatrixGate and simplify __eq__ in BasicGate to improve performance #288

Merged
merged 13 commits into from
Jan 30, 2019
1 change: 1 addition & 0 deletions docs/projectq.ops.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The operations collection consists of various default gates and is a work-in-pro
.. autosummary::

projectq.ops.BasicGate
projectq.ops.MatrixGate
projectq.ops.SelfInverseGate
projectq.ops.BasicRotationGate
projectq.ops.BasicPhaseGate
Expand Down
51 changes: 23 additions & 28 deletions projectq/backends/_sim/_simulator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from projectq import MainEngine
from projectq.cengines import (BasicEngine, BasicMapperEngine, DummyEngine,
LocalOptimizer, NotYetMeasuredError)
from projectq.ops import (All, Allocate, BasicGate, BasicMathGate, CNOT,
from projectq.ops import (All, Allocate, BasicGate, MatrixGate, BasicMathGate, CNOT,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line >80 characters and alphabetic order for inputs

Command, H, Measure, QubitOperator, Rx, Ry, Rz, S,
TimeEvolution, Toffoli, X, Y, Z)
from projectq.meta import Control, Dagger, LogicalQubitIDTag
Expand Down Expand Up @@ -97,37 +97,32 @@ def receive(self, command_list):
return None


class Mock1QubitGate(BasicGate):
def __init__(self):
BasicGate.__init__(self)
self.cnt = 0
class Mock1QubitGate(MatrixGate):
def __init__(self):
MatrixGate.__init__(self)
self.cnt = 0

@property
def matrix(self):
self.cnt += 1
return [[0, 1], [1, 0]]
@property
def matrix(self):
self.cnt += 1
return [[0, 1], [1, 0]]


class Mock6QubitGate(BasicGate):
def __init__(self):
BasicGate.__init__(self)
self.cnt = 0
class Mock6QubitGate(MatrixGate):
def __init__(self):
MatrixGate.__init__(self)
self.cnt = 0

@property
def matrix(self):
self.cnt += 1
return numpy.eye(2 ** 6)
@property
def matrix(self):
self.cnt += 1
return numpy.eye(2 ** 6)


class MockNoMatrixGate(BasicGate):
def __init__(self):
BasicGate.__init__(self)
self.cnt = 0

@property
def matrix(self):
self.cnt += 1
raise AttributeError
def __init__(self):
BasicGate.__init__(self)
self.cnt = 0


def test_simulator_is_available(sim):
Expand All @@ -147,15 +142,15 @@ def test_simulator_is_available(sim):

new_cmd.gate = Mock1QubitGate()
assert sim.is_available(new_cmd)
assert new_cmd.gate.cnt == 4
assert new_cmd.gate.cnt == 1

new_cmd.gate = Mock6QubitGate()
assert not sim.is_available(new_cmd)
assert new_cmd.gate.cnt == 4
assert new_cmd.gate.cnt == 1

new_cmd.gate = MockNoMatrixGate()
assert not sim.is_available(new_cmd)
assert new_cmd.gate.cnt == 7
assert new_cmd.gate.cnt == 0


def test_simulator_cheat(sim):
Expand Down
1 change: 1 addition & 0 deletions projectq/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ._basics import (NotMergeable,
NotInvertible,
BasicGate,
MatrixGate,
SelfInverseGate,
BasicRotationGate,
ClassicalInstructionGate,
Expand Down
84 changes: 52 additions & 32 deletions projectq/ops/_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,40 +203,20 @@ def __or__(self, qubits):
cmd = self.generate_command(qubits)
apply_command(cmd)

@property
def matrix(self):
raise AttributeError("BasicGate has no matrix property. Use MatrixGate instead.")

@matrix.setter
def matrix(self, matrix=None):
raise AttributeError("You cannot set the matrix property of gates derived"
"from BasicGate. Please use MatrixGate instead if"
"you need that functionality.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

White spaces are missing in error message


def __eq__(self, other):
""" Return True if equal (i.e., instance of same class).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better "Return True if instance of the same class, unless other is an instance of :class:MatrixGate, in which case equality is to be checked by testing for existence and (approximate) equality of matrix representations."


Unless both have a matrix attribute in which case we also check
that the matrices are identical as people might want to do the
following:

Example:
.. code-block:: python

gate = BasicGate()
gate.matrix = numpy.matrix([[1,0],[0, -1]])
"""
if hasattr(self, 'matrix'):
if not hasattr(other, 'matrix'):
return False
if hasattr(other, 'matrix'):
if not hasattr(self, 'matrix'):
return False
if hasattr(self, 'matrix') and hasattr(other, 'matrix'):
if (not isinstance(self.matrix, np.matrix) or
not isinstance(other.matrix, np.matrix)):
raise TypeError("One of the gates doesn't have the correct "
"type (numpy.matrix) for the matrix "
"attribute.")
if (self.matrix.shape == other.matrix.shape and
np.allclose(self.matrix, other.matrix,
rtol=RTOL, atol=ATOL,
equal_nan=False)):
return True
else:
return False
else:
return isinstance(other, self.__class__)
"""
return isinstance(other, self.__class__)

def __ne__(self, other):
return not self.__eq__(other)
Expand All @@ -247,6 +227,46 @@ def __str__(self):
def __hash__(self):
return hash(str(self))

cgogolin marked this conversation as resolved.
Show resolved Hide resolved
class MatrixGate(BasicGate):
"""
Defines a base class of a matrix gate.

A matrix gate is defined via its matrix.
"""
def __init__(self, matrix=None):
BasicGate.__init__(self)
self._matrix = np.matrix(matrix) if matrix is not None else None

@property
def matrix(self):
return self._matrix

@matrix.setter
def matrix(self, matrix):
self._matrix = np.matrix(matrix)

def __eq__(self, other):
""" Return True if equal (i.e., instance of same class).
"""
if not isinstance(other, self.__class__):
return False
if (not isinstance(self.matrix, np.matrix) or
not isinstance(other.matrix, np.matrix)):
raise TypeError("One of the gates doesn't have the correct "
"type (numpy.matrix) for the matrix "
"attribute.")
if (self.matrix.shape == other.matrix.shape and
np.allclose(self.matrix, other.matrix,
rtol=RTOL, atol=ATOL,
equal_nan=False)):
return True
return False

def __str__(self):
raise NotImplementedError('This gate does not implement __str__.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a __str__ and __hash__, e.g., for resource counting as this is now officially a gate which people can use directly


def get_inverse(self):
return MatrixGate(np.linalg.inv(self.matrix))

cgogolin marked this conversation as resolved.
Show resolved Hide resolved
class SelfInverseGate(BasicGate):
"""
Expand Down
34 changes: 30 additions & 4 deletions projectq/ops/_basics_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,12 @@ def test_basic_gate_compare():
gate2 = _basics.BasicGate()
assert gate1 == gate2
assert not (gate1 != gate2)
gate3 = _basics.BasicGate()
gate3 = _basics.MatrixGate()
gate3.matrix = np.matrix([[1, 0], [0, -1]])
assert gate1 != gate3
gate4 = _basics.BasicGate()
gate4 = _basics.MatrixGate()
gate4.matrix = [[1, 0], [0, -1]]
with pytest.raises(TypeError):
gate4 == gate3
assert gate4 == gate3


def test_comparing_different_gates():
Expand Down Expand Up @@ -295,3 +294,30 @@ def __init__(self):
# Test a=2, b=3, and c=5 should give a=2, b=3, c=11
math_fun = gate.get_math_function(("qreg1", "qreg2", "qreg3"))
assert math_fun([2, 3, 5]) == [2, 3, 11]

def test_matrix_gate():
gate1 = _basics.MatrixGate()
gate2 = _basics.MatrixGate()
with pytest.raises(TypeError):
assert gate1 == gate2
gate3 = _basics.MatrixGate([[0, 1], [1, 0]])
gate4 = _basics.MatrixGate([[0, 1], [1, 0]])
gate5 = _basics.MatrixGate([[1, 0], [0, -1]])
assert gate3 == gate4
assert gate4 != gate5
with pytest.raises(TypeError):
assert gate1 != gate3
with pytest.raises(TypeError):
assert gate3 != gate1
gate6 = _basics.BasicGate()
assert gate6 != gate1
assert gate6 != gate3
assert gate1 != gate6
assert gate3 != gate6
gate7 = gate5.get_inverse()
gate8 = _basics.MatrixGate([[1, 0], [0, (1+1j)/math.sqrt(2)]])
assert gate7 == gate5
assert gate7 != gate8
gate9 = _basics.MatrixGate([[1, 0], [0, (1-1j)/math.sqrt(2)]])
gate10 = gate9.get_inverse()
assert gate10 == gate8
1 change: 1 addition & 0 deletions projectq/ops/_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

from projectq.ops import get_inverse
from ._basics import (BasicGate,
MatrixGate,
SelfInverseGate,
BasicRotationGate,
BasicPhaseGate,
Expand Down
4 changes: 2 additions & 2 deletions projectq/ops/_metagates.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* C (Creates an n-ary controlled version of an arbitrary gate)
"""

from ._basics import BasicGate, NotInvertible
from ._basics import BasicGate, MatrixGate, NotInvertible
from ._command import Command, apply_command


Expand All @@ -39,7 +39,7 @@ class ControlQubitError(Exception):
pass


class DaggeredGate(BasicGate):
class DaggeredGate(MatrixGate): #todo: do we want this?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this should still be a BasicGate besides this introduces a bug, for example:

A DaggeredGate of QFT would now have a matrix property (because it is derived from MatrixGate) but it has no internal _matrix to return when DaggeredGate.matrix is called and will raise an AttributeError for _matrix which looks like an internal error

"""
Wrapper class allowing to execute the inverse of a gate, even when it does
not define one.
Expand Down
8 changes: 4 additions & 4 deletions projectq/setups/decompositions/arb1qubit2rzandry_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from projectq.backends import Simulator
from projectq.cengines import (AutoReplacer, DecompositionRuleSet,
DummyEngine, InstructionFilter, MainEngine)
from projectq.ops import (BasicGate, ClassicalInstructionGate, Measure, Ph, R,
from projectq.ops import (BasicGate, MatrixGate, ClassicalInstructionGate, Measure, Ph, R,
Rx, Ry, Rz, X)
from projectq.meta import Control

Expand Down Expand Up @@ -51,7 +51,7 @@ def test_recognize_incorrect_gates():
# Does not have matrix attribute:
BasicGate() | qubit
# Two qubit gate:
two_qubit_gate = BasicGate()
two_qubit_gate = MatrixGate()
two_qubit_gate.matrix = [[1, 0, 0, 0], [0, 1, 0, 0],
[0, 0, 1, 0], [0, 0, 0, 1]]
two_qubit_gate | qubit
Expand Down Expand Up @@ -121,7 +121,7 @@ def create_test_matrices():
def test_decomposition(gate_matrix):
for basis_state in ([1, 0], [0, 1]):
# Create single qubit gate with gate_matrix
test_gate = BasicGate()
test_gate = MatrixGate()
test_gate.matrix = np.matrix(gate_matrix)

correct_dummy_eng = DummyEngine(save_commands=True)
Expand Down Expand Up @@ -165,7 +165,7 @@ def test_decomposition(gate_matrix):
[[0, 2], [4, 0]],
[[1, 2], [4, 0]]])
def test_decomposition_errors(gate_matrix):
test_gate = BasicGate()
test_gate = MatrixGate()
test_gate.matrix = np.matrix(gate_matrix)
rule_set = DecompositionRuleSet(modules=[arb1q])
eng = MainEngine(backend=DummyEngine(),
Expand Down
6 changes: 3 additions & 3 deletions projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from projectq.cengines import (AutoReplacer, DecompositionRuleSet,
DummyEngine, InstructionFilter, MainEngine)
from projectq.meta import Control
from projectq.ops import (All, BasicGate, ClassicalInstructionGate, Measure,
from projectq.ops import (All, BasicGate, MatrixGate, ClassicalInstructionGate, Measure,
Ph, R, Rx, Ry, Rz, X, XGate)
from projectq.setups.decompositions import arb1qubit2rzandry_test as arb1q_t

Expand Down Expand Up @@ -57,7 +57,7 @@ def test_recognize_incorrect_gates():
# Does not have matrix attribute:
BasicGate() | qubit
# Two qubit gate:
two_qubit_gate = BasicGate()
two_qubit_gate = MatrixGate()
two_qubit_gate.matrix = np.matrix([[1, 0, 0, 0], [0, 1, 0, 0],
[0, 0, 1, 0], [0, 0, 0, 1]])
two_qubit_gate | qubit
Expand Down Expand Up @@ -94,7 +94,7 @@ def test_recognize_v(gate_matrix):
@pytest.mark.parametrize("gate_matrix", arb1q_t.create_test_matrices())
def test_decomposition(gate_matrix):
# Create single qubit gate with gate_matrix
test_gate = BasicGate()
test_gate = MatrixGate()
test_gate.matrix = np.matrix(gate_matrix)

for basis_state in ([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0],
Expand Down