diff --git a/docs/projectq.ops.rst b/docs/projectq.ops.rst index 8934dd68c..8fbe2e287 100755 --- a/docs/projectq.ops.rst +++ b/docs/projectq.ops.rst @@ -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 diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 546ab3584..9e301be0d 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -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 @@ -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): @@ -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): diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 32ff8ab54..db4a38b79 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -15,6 +15,7 @@ from ._basics import (NotMergeable, NotInvertible, BasicGate, + MatrixGate, SelfInverseGate, BasicRotationGate, ClassicalInstructionGate, diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 7f724c93e..4bca84429 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -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): """ @@ -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) @@ -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. diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 61a1de767..80cc80183 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -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 @@ -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(): @@ -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]])") diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index d160dc9e3..d524e4b3b 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -37,6 +37,7 @@ from projectq.ops import get_inverse from ._basics import (BasicGate, + MatrixGate, SelfInverseGate, BasicRotationGate, BasicPhaseGate, diff --git a/projectq/setups/decompositions/arb1qubit2rzandry_test.py b/projectq/setups/decompositions/arb1qubit2rzandry_test.py index 90eba5edf..02ec907d6 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry_test.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry_test.py @@ -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 @@ -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 @@ -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) @@ -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(), diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py index 7d324e81c..44b29f526 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py @@ -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 @@ -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 @@ -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],