Skip to content

Commit

Permalink
Implement MatrixGate and simplify __eq__ in BasicGate to improve perf…
Browse files Browse the repository at this point in the history
…ormance (#288)
  • Loading branch information
cgogolin authored and damiansteiger committed Jan 30, 2019
1 parent ef99f48 commit eaf1334
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 76 deletions.
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
111 changes: 78 additions & 33 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 @@ -204,39 +204,19 @@ def __or__(self, qubits):
apply_command(cmd)

def __eq__(self, other):
""" Return True if equal (i.e., instance of same class).
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
"""
Equality comparision
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.
"""
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 +228,71 @@ def __hash__(self):
return hash(str(self))


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):
"""
Equality comparision
Return True only if both gates have a matrix respresentation and the
matrices are (approximately) equal. Otherwise return False.
"""
if not hasattr(other, 'matrix'):
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):
return("MatrixGate(" + str(self.matrix.tolist()) + ")")

def __hash__(self):
return hash(str(self))

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


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

0 comments on commit eaf1334

Please sign in to comment.