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
53 changes: 24 additions & 29 deletions projectq/backends/_sim/_simulator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
from projectq.cengines import (BasicEngine, BasicMapperEngine, DummyEngine,
LocalOptimizer, NotYetMeasuredError)
from projectq.ops import (All, Allocate, BasicGate, BasicMathGate, CNOT,
Command, H, Measure, QubitOperator, Rx, Ry, Rz, S,
TimeEvolution, Toffoli, X, Y, Z)
Command, H, MatrixGate, Measure, QubitOperator,
Rx, Ry, Rz, S, TimeEvolution, Toffoli, X, Y, Z)
from projectq.meta import Control, Dagger, LogicalQubitIDTag
from projectq.types import WeakQubitRef

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
101 changes: 69 additions & 32 deletions projectq/ops/_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class NotInvertible(Exception):

class BasicGate(object):
"""
Base class of all gates.
Base class of all gates. (Don't use it directly but derive from it)
"""
def __init__(self):
"""
Expand Down Expand Up @@ -205,38 +205,13 @@ def __or__(self, qubits):

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
"""
if isinstance(other, self.__class__):
return True
elif isinstance(other, MatrixGate):
return NotImplemented
else:
return isinstance(other, self.__class__)
return False

def __ne__(self, other):
return not self.__eq__(other)
Expand All @@ -248,6 +223,68 @@ def __hash__(self):
return hash(str(self))

cgogolin marked this conversation as resolved.
Show resolved Hide resolved

class MatrixGate(BasicGate):
"""
Defines a gate class whose instances are defined by a matrix.

Note:
Use this gate class only for gates acting on a small numbers of qubits.
In general, consider instead using one of the provided ProjectQ gates
or define a new class as this allows the compiler to work symbolically.

Example:

.. code-block:: python

gate = MatrixGate([[0, 1], [1, 0]])
gate | qubit
"""
def __init__(self, matrix=None):
"""
Initialize MatrixGate

Args:
matrix(numpy.matrix): matrix which defines the gate. Default: 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):
if not hasattr(other, 'matrix'):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I achnowledge that the docstring was not good, but do you really want no docstring at all?

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

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 __ne__(self, other):
return not self.__eq__(other)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why is that necessary? Isn't __ne__() with the same implementation inherited from BasicGate?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry my bad, of course it uses __ne__ from BasicGate.
(It's a habit from Python2's old classes)

def __str__(self):
return("MatrixGate(" + str(self.matrix.tolist()) + ")")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why do you convert to list here? Isn't it better if it is visible in the output that self.matrix is a numpy array (i.e., no commas between elements vs. commas between elements for lists)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Just so that the CommandPrinter prints such a gate on one line. A numpy matrix string has line breaks which then looks slightly weird to me.

We can use numpy arrays for this string once we switch from numpy matrix to numpy arrays #287


def __hash__(self):
return hash(str(self))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Again, this seems superflous because __hash__() is inherited.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

Choose a reason for hiding this comment

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

uups. Python3 seems to remove hash if eq is redefined...


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

cgogolin marked this conversation as resolved.
Show resolved Hide resolved

class SelfInverseGate(BasicGate):
"""
Self-inverse basic gate class.
Expand Down
41 changes: 36 additions & 5 deletions projectq/ops/_basics_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import pytest

from projectq.types import Qubit, Qureg
from projectq.ops import Command
from projectq.ops import Command, X
from projectq import MainEngine
from projectq.cengines import DummyEngine

Expand Down 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,35 @@ 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
assert gate3 == X
assert X == gate3
assert str(gate3) == "MatrixGate([[0, 1], [1, 0]])"
assert hash(gate3) == hash("MatrixGate([[0, 1], [1, 0]])")
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
10 changes: 5 additions & 5 deletions projectq/setups/decompositions/arb1qubit2rzandry_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
from projectq.backends import Simulator
from projectq.cengines import (AutoReplacer, DecompositionRuleSet,
DummyEngine, InstructionFilter, MainEngine)
from projectq.ops import (BasicGate, ClassicalInstructionGate, Measure, Ph, R,
Rx, Ry, Rz, X)
from projectq.ops import (BasicGate, ClassicalInstructionGate, MatrixGate,
Measure, Ph, R, Rx, Ry, Rz, X)
from projectq.meta import Control

from . import arb1qubit2rzandry as arb1q
Expand All @@ -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
8 changes: 4 additions & 4 deletions projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
from projectq.cengines import (AutoReplacer, DecompositionRuleSet,
DummyEngine, InstructionFilter, MainEngine)
from projectq.meta import Control
from projectq.ops import (All, BasicGate, ClassicalInstructionGate, Measure,
Ph, R, Rx, Ry, Rz, X, XGate)
from projectq.ops import (All, BasicGate, ClassicalInstructionGate,
MatrixGate, Measure, Ph, R, Rx, Ry, Rz, X, XGate)
from projectq.setups.decompositions import arb1qubit2rzandry_test as arb1q_t

from . import carb1qubit2cnotrzandry as carb1q
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