diff --git a/docs/projectq.setups.decompositions.rst b/docs/projectq.setups.decompositions.rst index 26cd392fc..6206c4a95 100755 --- a/docs/projectq.setups.decompositions.rst +++ b/docs/projectq.setups.decompositions.rst @@ -10,16 +10,19 @@ The decomposition package is a collection of gate decomposition / replacement ru projectq.setups.decompositions.barrier projectq.setups.decompositions.carb1qubit2cnotrzandry projectq.setups.decompositions.cnot2cz + projectq.setups.decompositions.cnot2rxx projectq.setups.decompositions.cnu2toffoliandcu projectq.setups.decompositions.crz2cxandrz projectq.setups.decompositions.entangle projectq.setups.decompositions.globalphase + projectq.setups.decompositions.h2rx projectq.setups.decompositions.ph2r projectq.setups.decompositions.qft2crandhadamard projectq.setups.decompositions.qubitop2onequbit projectq.setups.decompositions.r2rzandph projectq.setups.decompositions.rx2rz projectq.setups.decompositions.ry2rz + projectq.setups.decompositions.rz2rx projectq.setups.decompositions.sqrtswap2cnot projectq.setups.decompositions.stateprep2cnot projectq.setups.decompositions.swap2cnot @@ -62,6 +65,13 @@ projectq.setups.decompositions.cnot2cz module :members: :undoc-members: +projectq.setups.decompositions.cnot2rxx module +--------------------------------------------- + +.. automodule:: projectq.setups.decompositions.cnot2rxx + :members: + :undoc-members: + projectq.setups.decompositions.cnu2toffoliandcu module ------------------------------------------------------ @@ -90,7 +100,8 @@ projectq.setups.decompositions.globalphase module :members: :undoc-members: -projectq.setups.decompositions.ph2r module + +projectq.setups.decompositions.h2rx module ------------------------------------------ .. automodule:: projectq.setups.decompositions.ph2r @@ -132,6 +143,13 @@ projectq.setups.decompositions.ry2rz module :members: :undoc-members: +projectq.setups.decompositions.rz2rx module +------------------------------------------- + +.. automodule:: projectq.setups.decompositions.rz2rx + :members: + :undoc-members: + projectq.setups.decompositions.sqrtswap2cnot module --------------------------------------------------- diff --git a/docs/projectq.setups.rst b/docs/projectq.setups.rst index 058469f07..98e7e611a 100755 --- a/docs/projectq.setups.rst +++ b/docs/projectq.setups.rst @@ -86,6 +86,13 @@ restrictedgateset :special-members: __init__ :undoc-members: +trapped_ion_decomposer +---------------------- +.. automodule:: projectq.setups.trapped_ion_decomposer + :members: + :special-members: __init__ + :undoc-members: + Module contents --------------- diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 2e72540b9..0c9765288 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -116,7 +116,7 @@ def _get_gate_indices(self, idx, i, IDs): def _optimize(self, idx, lim=None): """ - Try to merge or even cancel successive gates using the get_merged and + Try to remove identity gates using the is_identity function, then merge or even cancel successive gates using the get_merged and get_inverse functions of the gate (see, e.g., BasicRotationGate). It does so for all qubit command lists. @@ -130,6 +130,20 @@ def _optimize(self, idx, lim=None): new_gateloc = limit while i < limit - 1: + # can be dropped if the gate is equivalent to an identity gate + if self._l[idx][i].is_identity(): + # determine index of this gate on all qubits + qubitids = [qb.id for sublist in self._l[idx][i].all_qubits + for qb in sublist] + gid = self._get_gate_indices(idx, i, qubitids) + for j in range(len(qubitids)): + new_list = (self._l[qubitids[j]][0:gid[j]] + + self._l[qubitids[j]][gid[j] +1:]) + self._l[qubitids[j]] = new_list + i = 0 + limit -= 1 + continue + # can be dropped if two in a row are self-inverses inv = self._l[idx][i].get_inverse() diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index e0196f83b..121dbb471 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -16,6 +16,7 @@ import pytest +import math from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.ops import (CNOT, H, Rx, Ry, AllocateQubitGate, X, @@ -127,3 +128,22 @@ def test_local_optimizer_mergeable_gates(): # Expect allocate, one Rx gate, and flush gate assert len(backend.received_commands) == 3 assert backend.received_commands[1].gate == Rx(10 * 0.5) + + +def test_local_optimizer_identity_gates(): + local_optimizer = _optimize.LocalOptimizer(m=4) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[local_optimizer]) + # Test that it merges mergeable gates such as Rx + qb0 = eng.allocate_qubit() + for _ in range(10): + Rx(0.0) | qb0 + Ry(0.0) | qb0 + Rx(4*math.pi) | qb0 + Ry(4*math.pi) | qb0 + Rx(0.5) | qb0 + assert len(backend.received_commands) == 0 + eng.flush() + # Expect allocate, one Rx gate, and flush gate + assert len(backend.received_commands) == 3 + assert backend.received_commands[1].gate == Rx(0.5) diff --git a/projectq/cengines/_replacer/_replacer.py b/projectq/cengines/_replacer/_replacer.py index 29c883123..85fa303dc 100755 --- a/projectq/cengines/_replacer/_replacer.py +++ b/projectq/cengines/_replacer/_replacer.py @@ -175,7 +175,6 @@ def _process_command(self, cmd): # use decomposition chooser to determine the best decomposition chosen_decomp = self._decomp_chooser(cmd, decomp_list) - # the decomposed command must have the same tags # (plus the ones it gets from meta-statements inside the # decomposition rule). diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index cac384d9e..dd73cc2d5 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -25,6 +25,7 @@ from ._command import apply_command, Command from ._metagates import (DaggeredGate, get_inverse, + is_identity, ControlledGate, C, Tensor, diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 4bca84429..c7bdd31bc 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Defines the BasicGate class, the base class of all gates, the BasicRotationGate class, the SelfInverseGate, the FastForwardingGate, the @@ -39,9 +38,10 @@ from projectq.types import BasicQubit from ._command import Command, apply_command +import unicodedata ANGLE_PRECISION = 12 -ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION +ANGLE_TOLERANCE = 10**-ANGLE_PRECISION RTOL = 1e-10 ATOL = 1e-12 @@ -157,7 +157,7 @@ def make_tuple_of_qureg(qubits): (or list of Qubits) objects. """ if not isinstance(qubits, tuple): - qubits = (qubits,) + qubits = (qubits, ) qubits = list(qubits) @@ -208,8 +208,9 @@ def __eq__(self, other): 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. + 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 @@ -224,9 +225,21 @@ def __ne__(self, other): def __str__(self): raise NotImplementedError('This gate does not implement __str__.') + def to_string(self, symbols): + """ + String representation + + Achieve same function as str() but can be extended for configurable + representation + """ + return str(self) + def __hash__(self): return hash(str(self)) + def is_identity(self): + return False + class MatrixGate(BasicGate): """ @@ -271,20 +284,19 @@ def __eq__(self, other): """ if not hasattr(other, 'matrix'): return False - if (not isinstance(self.matrix, np.matrix) or - not isinstance(other.matrix, np.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)): + 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()) + ")") + return ("MatrixGate(" + str(self.matrix.tolist()) + ")") def __hash__(self): return hash(str(self)) @@ -343,7 +355,23 @@ def __str__(self): [CLASSNAME]([ANGLE]) """ - return str(self.__class__.__name__) + "(" + str(self.angle) + ")" + return self.to_string() + + def to_string(self, symbols=False): + """ + Return the string representation of a BasicRotationGate. + + Args: + symbols (bool): uses the pi character and round the angle for a + more user friendly display if True, full angle + written in radian otherwise. + """ + if symbols: + angle = ("(" + str(round(self.angle / math.pi, 3)) + + unicodedata.lookup("GREEK SMALL LETTER PI") + ")") + else: + angle = "(" + str(self.angle) + ")" + return str(self.__class__.__name__) + angle def tex_str(self): """ @@ -355,7 +383,8 @@ def tex_str(self): [CLASSNAME]$_[ANGLE]$ """ - return str(self.__class__.__name__) + "$_{" + str(self.angle) + "}$" + return (str(self.__class__.__name__) + "$_{" + + str(round(self.angle / math.pi, 3)) + "\\pi}$") def get_inverse(self): """ @@ -401,6 +430,12 @@ def __ne__(self, other): def __hash__(self): return hash(str(self)) + def is_identity(self): + """ + Return True if the gate is equivalent to an Identity gate + """ + return self.angle == 0. or self.angle == 4 * math.pi + class BasicPhaseGate(BasicGate): """ @@ -597,6 +632,7 @@ def math_fun(a): def math_function(x): return list(math_fun(*x)) + self._math_function = math_function def __str__(self): diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 80cc80183..a58a24e4c 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,10 +12,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.ops._basics.""" -from copy import deepcopy import math import numpy as np @@ -49,13 +48,13 @@ def test_basic_gate_make_tuple_of_qureg(main_engine): qubit3 = Qubit(main_engine, 3) qureg = Qureg([qubit2, qubit3]) case1 = _basics.BasicGate.make_tuple_of_qureg(qubit0) - assert case1 == ([qubit0],) + assert case1 == ([qubit0], ) case2 = _basics.BasicGate.make_tuple_of_qureg([qubit0, qubit1]) - assert case2 == ([qubit0, qubit1],) + assert case2 == ([qubit0, qubit1], ) case3 = _basics.BasicGate.make_tuple_of_qureg(qureg) - assert case3 == (qureg,) - case4 = _basics.BasicGate.make_tuple_of_qureg((qubit0,)) - assert case4 == ([qubit0],) + assert case3 == (qureg, ) + case4 = _basics.BasicGate.make_tuple_of_qureg((qubit0, )) + assert case4 == ([qubit0], ) case5 = _basics.BasicGate.make_tuple_of_qureg((qureg, qubit0)) assert case5 == (qureg, [qubit0]) @@ -68,20 +67,15 @@ def test_basic_gate_generate_command(main_engine): qureg = Qureg([qubit2, qubit3]) basic_gate = _basics.BasicGate() command1 = basic_gate.generate_command(qubit0) - assert command1 == Command(main_engine, basic_gate, - ([qubit0],)) + assert command1 == Command(main_engine, basic_gate, ([qubit0], )) command2 = basic_gate.generate_command([qubit0, qubit1]) - assert command2 == Command(main_engine, basic_gate, - ([qubit0, qubit1],)) + assert command2 == Command(main_engine, basic_gate, ([qubit0, qubit1], )) command3 = basic_gate.generate_command(qureg) - assert command3 == Command(main_engine, basic_gate, - (qureg,)) - command4 = basic_gate.generate_command((qubit0,)) - assert command4 == Command(main_engine, basic_gate, - ([qubit0],)) + assert command3 == Command(main_engine, basic_gate, (qureg, )) + command4 = basic_gate.generate_command((qubit0, )) + assert command4 == Command(main_engine, basic_gate, ([qubit0], )) command5 = basic_gate.generate_command((qureg, qubit0)) - assert command5 == Command(main_engine, basic_gate, - (qureg, [qubit0])) + assert command5 == Command(main_engine, basic_gate, (qureg, [qubit0])) def test_basic_gate_or(): @@ -100,8 +94,8 @@ def test_basic_gate_or(): basic_gate | [qubit0, qubit1] command3 = basic_gate.generate_command(qureg) basic_gate | qureg - command4 = basic_gate.generate_command((qubit0,)) - basic_gate | (qubit0,) + command4 = basic_gate.generate_command((qubit0, )) + basic_gate | (qubit0, ) command5 = basic_gate.generate_command((qureg, qubit0)) basic_gate | (qureg, qubit0) received_commands = [] @@ -109,8 +103,9 @@ def test_basic_gate_or(): for cmd in saving_backend.received_commands: if not isinstance(cmd.gate, _basics.FastForwardingGate): received_commands.append(cmd) - assert received_commands == ([command1, command2, command3, command4, - command5]) + assert received_commands == ([ + command1, command2, command3, command4, command5 + ]) def test_basic_gate_compare(): @@ -163,15 +158,17 @@ def test_basic_rotation_gate_init(input_angle, modulo_angle): def test_basic_rotation_gate_str(): - basic_rotation_gate = _basics.BasicRotationGate(0.5) - assert str(basic_rotation_gate) == "BasicRotationGate(0.5)" + gate = _basics.BasicRotationGate(math.pi) + assert str(gate) == "BasicRotationGate(3.14159265359)" + assert gate.to_string(symbols=True) == u"BasicRotationGate(1.0π)" + assert gate.to_string(symbols=False) == "BasicRotationGate(3.14159265359)" def test_basic_rotation_tex_str(): - basic_rotation_gate = _basics.BasicRotationGate(0.5) - assert basic_rotation_gate.tex_str() == "BasicRotationGate$_{0.5}$" - basic_rotation_gate = _basics.BasicRotationGate(4 * math.pi - 1e-13) - assert basic_rotation_gate.tex_str() == "BasicRotationGate$_{0.0}$" + gate = _basics.BasicRotationGate(0.5 * math.pi) + assert gate.tex_str() == "BasicRotationGate$_{0.5\\pi}$" + gate = _basics.BasicRotationGate(4 * math.pi - 1e-13) + assert gate.tex_str() == "BasicRotationGate$_{0.0\\pi}$" @pytest.mark.parametrize("input_angle, inverse_angle", @@ -194,6 +191,19 @@ def test_basic_rotation_gate_get_merged(): assert merged_gate == basic_rotation_gate3 +def test_basic_rotation_gate_is_identity(): + basic_rotation_gate1 = _basics.BasicRotationGate(0.) + basic_rotation_gate2 = _basics.BasicRotationGate(1. * math.pi) + basic_rotation_gate3 = _basics.BasicRotationGate(2. * math.pi) + basic_rotation_gate4 = _basics.BasicRotationGate(3. * math.pi) + basic_rotation_gate5 = _basics.BasicRotationGate(4. * math.pi) + assert basic_rotation_gate1.is_identity() + assert not basic_rotation_gate2.is_identity() + assert not basic_rotation_gate3.is_identity() + assert not basic_rotation_gate4.is_identity() + assert basic_rotation_gate5.is_identity() + + def test_basic_rotation_gate_comparison_and_hash(): basic_rotation_gate1 = _basics.BasicRotationGate(0.5) basic_rotation_gate2 = _basics.BasicRotationGate(0.5) @@ -316,10 +326,10 @@ def test_matrix_gate(): assert gate1 != gate6 assert gate3 != gate6 gate7 = gate5.get_inverse() - gate8 = _basics.MatrixGate([[1, 0], [0, (1+1j)/math.sqrt(2)]]) + 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)]]) + gate9 = _basics.MatrixGate([[1, 0], [0, (1 - 1j) / math.sqrt(2)]]) gate10 = gate9.get_inverse() assert gate10 == gate8 assert gate3 == X diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 5186502fa..6c320f375 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ This file defines the apply_command function and the Command class. @@ -82,7 +81,6 @@ class Command(object): and hence adds its LoopTag to the end. all_qubits: A tuple of control_qubits + qubits """ - def __init__(self, engine, gate, qubits, controls=(), tags=()): """ Initialize a Command object. @@ -106,9 +104,9 @@ def __init__(self, engine, gate, qubits, controls=(), tags=()): tags (list[object]): Tags associated with the command. """ - qubits = tuple([WeakQubitRef(qubit.engine, qubit.id) - for qubit in qreg] - for qreg in qubits) + qubits = tuple( + [WeakQubitRef(qubit.engine, qubit.id) for qubit in qreg] + for qreg in qubits) self.gate = gate self.tags = list(tags) @@ -126,11 +124,8 @@ def qubits(self, qubits): def __deepcopy__(self, memo): """ Deepcopy implementation. Engine should stay a reference.""" - return Command(self.engine, - deepcopy(self.gate), - self.qubits, - list(self.control_qubits), - deepcopy(self.tags)) + return Command(self.engine, deepcopy(self.gate), self.qubits, + list(self.control_qubits), deepcopy(self.tags)) def get_inverse(self): """ @@ -143,12 +138,19 @@ def get_inverse(self): NotInvertible: If the gate does not provide an inverse (see BasicGate.get_inverse) """ - return Command(self._engine, - projectq.ops.get_inverse(self.gate), - self.qubits, - list(self.control_qubits), + return Command(self._engine, projectq.ops.get_inverse(self.gate), + self.qubits, list(self.control_qubits), deepcopy(self.tags)) + def is_identity(self): + """ + Evaluate if the gate called in the command object is an identity gate. + + Returns: + True if the gate is equivalent to an Identity gate, False otherwise + """ + return projectq.ops.is_identity(self.gate) + def get_merged(self, other): """ Merge this command with another one and return the merged command @@ -161,12 +163,10 @@ def get_merged(self, other): NotMergeable: if the gates don't supply a get_merged()-function or can't be merged for other reasons. """ - if (self.tags == other.tags and self.all_qubits == other.all_qubits and - self.engine == other.engine): - return Command(self.engine, - self.gate.get_merged(other.gate), - self.qubits, - self.control_qubits, + if (self.tags == other.tags and self.all_qubits == other.all_qubits + and self.engine == other.engine): + return Command(self.engine, self.gate.get_merged(other.gate), + self.qubits, self.control_qubits, deepcopy(self.tags)) raise projectq.ops.NotMergeable("Commands not mergeable.") @@ -219,8 +219,9 @@ def control_qubits(self, qubits): Args: control_qubits (Qureg): quantum register """ - self._control_qubits = ([WeakQubitRef(qubit.engine, qubit.id) - for qubit in qubits]) + self._control_qubits = ([ + WeakQubitRef(qubit.engine, qubit.id) for qubit in qubits + ]) self._control_qubits = sorted(self._control_qubits, key=lambda x: x.id) def add_control_qubits(self, qubits): @@ -236,9 +237,9 @@ def add_control_qubits(self, qubits): gate, i.e., the gate is only executed if all qubits are in state 1. """ - assert(isinstance(qubits, list)) - self._control_qubits.extend([WeakQubitRef(qubit.engine, qubit.id) - for qubit in qubits]) + assert (isinstance(qubits, list)) + self._control_qubits.extend( + [WeakQubitRef(qubit.engine, qubit.id) for qubit in qubits]) self._control_qubits = sorted(self._control_qubits, key=lambda x: x.id) @property @@ -250,7 +251,7 @@ def all_qubits(self): WeakQubitRef objects) containing the control qubits and T[1:] contains the quantum registers to which the gate is applied. """ - return (self.control_qubits,) + self.qubits + return (self.control_qubits, ) + self.qubits @property def engine(self): @@ -285,11 +286,9 @@ def __eq__(self, other): Returns: True if Command objects are equal (same gate, applied to same qubits; ordered modulo interchangeability; and same tags) """ - if (isinstance(other, self.__class__) and - self.gate == other.gate and - self.tags == other.tags and - self.engine == other.engine and - self.all_qubits == other.all_qubits): + if (isinstance(other, self.__class__) and self.gate == other.gate + and self.tags == other.tags and self.engine == other.engine + and self.all_qubits == other.all_qubits): return True return False @@ -297,13 +296,16 @@ def __ne__(self, other): return not self.__eq__(other) def __str__(self): + return self.to_string() + + def to_string(self, symbols=False): """ Get string representation of this Command object. """ qubits = self.qubits ctrlqubits = self.control_qubits if len(ctrlqubits) > 0: - qubits = (self.control_qubits,) + qubits + qubits = (self.control_qubits, ) + qubits qstring = "" if len(qubits) == 1: qstring = str(Qureg(qubits[0])) @@ -314,4 +316,4 @@ def __str__(self): qstring += ", " qstring = qstring[:-2] + " )" cstring = "C" * len(ctrlqubits) - return cstring + str(self.gate) + " | " + qstring + return cstring + self.gate.to_string(symbols) + " | " + qstring diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index ae1407836..b0b4d54c8 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +16,7 @@ """Tests for projectq.ops._command.""" from copy import deepcopy +import sys import math import pytest @@ -36,8 +38,8 @@ def test_command_init(main_engine): qureg0 = Qureg([Qubit(main_engine, 0)]) qureg1 = Qureg([Qubit(main_engine, 1)]) qureg2 = Qureg([Qubit(main_engine, 2)]) - qureg3 = Qureg([Qubit(main_engine, 3)]) - qureg4 = Qureg([Qubit(main_engine, 4)]) + # qureg3 = Qureg([Qubit(main_engine, 3)]) + # qureg4 = Qureg([Qubit(main_engine, 4)]) gate = BasicGate() cmd = _command.Command(main_engine, gate, (qureg0, qureg1, qureg2)) assert cmd.gate == gate @@ -133,6 +135,19 @@ def test_command_get_merged(main_engine): cmd.get_merged(cmd4) +def test_command_is_identity(main_engine): + qubit = main_engine.allocate_qubit() + qubit2 = main_engine.allocate_qubit() + cmd = _command.Command(main_engine, Rx(0.), (qubit,)) + cmd2 = _command.Command(main_engine, Rx(0.5), (qubit2,)) + inverse_cmd = cmd.get_inverse() + inverse_cmd2 = cmd2.get_inverse() + assert inverse_cmd.gate.is_identity() + assert cmd.gate.is_identity() + assert not inverse_cmd2.gate.is_identity() + assert not cmd2.gate.is_identity() + + def test_command_order_qubits(main_engine): qubit0 = Qureg([Qubit(main_engine, 0)]) qubit1 = Qureg([Qubit(main_engine, 1)]) @@ -232,9 +247,32 @@ def test_command_comparison(main_engine): def test_command_str(): qubit = Qureg([Qubit(main_engine, 0)]) ctrl_qubit = Qureg([Qubit(main_engine, 1)]) - cmd = _command.Command(main_engine, Rx(0.5), (qubit,)) + cmd = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) cmd.tags = ["TestTag"] cmd.add_control_qubits(ctrl_qubit) - assert str(cmd) == "CRx(0.5) | ( Qureg[1], Qureg[0] )" - cmd2 = _command.Command(main_engine, Rx(0.5), (qubit,)) - assert str(cmd2) == "Rx(0.5) | Qureg[0]" + cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) + if sys.version_info.major == 3: + assert cmd.to_string(symbols=False) == "CRx(1.570796326795) | ( Qureg[1], Qureg[0] )" + assert str(cmd2) == "Rx(1.570796326795) | Qureg[0]" + else: + assert cmd.to_string(symbols=False) == "CRx(1.5707963268) | ( Qureg[1], Qureg[0] )" + assert str(cmd2) == "Rx(1.5707963268) | Qureg[0]" + + +def test_command_to_string(): + qubit = Qureg([Qubit(main_engine, 0)]) + ctrl_qubit = Qureg([Qubit(main_engine, 1)]) + cmd = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) + cmd.tags = ["TestTag"] + cmd.add_control_qubits(ctrl_qubit) + cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) + + assert cmd.to_string(symbols=True) == u"CRx(0.5π) | ( Qureg[1], Qureg[0] )" + assert cmd2.to_string(symbols=True) == u"Rx(0.5π) | Qureg[0]" + if sys.version_info.major == 3: + assert cmd.to_string(symbols=False) == "CRx(1.570796326795) | ( Qureg[1], Qureg[0] )" + assert cmd2.to_string(symbols=False) == "Rx(1.570796326795) | Qureg[0]" + else: + assert cmd.to_string(symbols=False) == "CRx(1.5707963268) | ( Qureg[1], Qureg[0] )" + assert cmd2.to_string(symbols=False) == "Rx(1.5707963268) | Qureg[0]" + diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py old mode 100755 new mode 100644 index b2e7959fe..cca5e7412 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -132,6 +132,22 @@ def get_inverse(gate): except NotInvertible: return DaggeredGate(gate) +def is_identity(gate): + """ + Return True if the gate is an identity gate. + + Tries to call gate.is_identity and, upon failure, returns False + + Args: + gate: Gate of which to get the inverse + + Example: + .. code-block:: python + + get_inverse(Rx(2*math.pi)) # returns True + get_inverse(Rx(math.pi)) # returns False + """ + return gate.is_identity() class ControlledGate(BasicGate): """ diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py index c42393b06..8632a99e5 100755 --- a/projectq/ops/_metagates_test.py +++ b/projectq/ops/_metagates_test.py @@ -122,6 +122,16 @@ def test_get_inverse(): inv2 = _metagates.get_inverse(invertible_gate) assert inv2 == Y +def test_is_identity(): + # Choose gate which is not an identity gate: + non_identity_gate=Rx(0.5) + assert not non_identity_gate.is_identity() + assert not _metagates.is_identity(non_identity_gate) + # Choose gate which is an identity gate: + identity_gate=Rx(0.) + assert identity_gate.is_identity() + assert _metagates.is_identity(identity_gate) + def test_controlled_gate_init(): one_control = _metagates.ControlledGate(Y, 1) diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index 883f04581..de557a065 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -16,16 +16,19 @@ barrier, carb1qubit2cnotrzandry, crz2cxandrz, + cnot2rxx, cnot2cz, cnu2toffoliandcu, entangle, globalphase, + h2rx, ph2r, qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, ry2rz, + rz2rx, sqrtswap2cnot, stateprep2cnot, swap2cnot, @@ -41,16 +44,19 @@ barrier, carb1qubit2cnotrzandry, crz2cxandrz, + cnot2rxx, cnot2cz, cnu2toffoliandcu, entangle, globalphase, + h2rx, ph2r, qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, ry2rz, + rz2rx, sqrtswap2cnot, stateprep2cnot, swap2cnot, diff --git a/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py new file mode 100644 index 000000000..a1fa2e6ac --- /dev/null +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -0,0 +1,61 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Module uses ideas from "Basic circuit compilation techniques +# for an ion-trap quantum machine" by Dmitri Maslov (2017) at +# https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 +""" +Registers a decomposition to for a CNOT gate in terms of Rxx, Rx and Ry gates. +""" + +from projectq.cengines import DecompositionRule +from projectq.meta import get_control_count +from projectq.ops import Ph, Rxx, Ry, Rx, X +import math + + +def _decompose_cnot2rxx_M(cmd): + """ Decompose CNOT gate into Rxx gate. """ + # Labelled 'M' for 'minus' because decomposition ends with a Ry(-pi/2) + ctrl = cmd.control_qubits + Ry(math.pi / 2) | ctrl[0] + Ph(7 * math.pi / 4) | ctrl[0] + Rx(-math.pi / 2) | ctrl[0] + Rx(-math.pi / 2) | cmd.qubits[0][0] + Rxx(math.pi / 2) | (ctrl[0], cmd.qubits[0][0]) + Ry(-1 * math.pi / 2) | ctrl[0] + + +def _decompose_cnot2rxx_P(cmd): + """ Decompose CNOT gate into Rxx gate. """ + # Labelled 'P' for 'plus' because decomposition ends with a Ry(+pi/2) + ctrl = cmd.control_qubits + Ry(-math.pi / 2) | ctrl[0] + Ph(math.pi / 4) | ctrl[0] + Rx(-math.pi / 2) | ctrl[0] + Rx(math.pi / 2) | cmd.qubits[0][0] + Rxx(math.pi / 2) | (ctrl[0], cmd.qubits[0][0]) + Ry(math.pi / 2) | ctrl[0] + + +def _recognize_cnot2(cmd): + """ Identify that the command is a CNOT gate (control - X gate)""" + return get_control_count(cmd) == 1 + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(X.__class__, _decompose_cnot2rxx_M, _recognize_cnot2), + DecompositionRule(X.__class__, _decompose_cnot2rxx_P, _recognize_cnot2) +] diff --git a/projectq/setups/decompositions/cnot2rxx_test.py b/projectq/setups/decompositions/cnot2rxx_test.py new file mode 100644 index 000000000..bc0d0c077 --- /dev/null +++ b/projectq/setups/decompositions/cnot2rxx_test.py @@ -0,0 +1,124 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Tests for projectq.setups.decompositions.cnot2rxx.py." + +import pytest +import numpy as np + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, + InstructionFilter) +from projectq.meta import Control +from projectq.ops import All, CNOT, CZ, Measure, X, Z + +from . import cnot2rxx + + +def test_recognize_correct_gates(): + """Test that recognize_cnot recognizes cnot gates. """ + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend) + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + qubit3 = eng.allocate_qubit() + eng.flush() + # Create a control function in 3 different ways + CZ | (qubit1, qubit2) + with Control(eng, qubit2): + Z | qubit1 + X | qubit1 + with Control(eng, qubit2 + qubit3): + Z | qubit1 + eng.flush() + eng.flush(deallocate_qubits=True) + for cmd in saving_backend.received_commands[4:7]: + assert cnot2rxx._recognize_cnot2(cmd) + for cmd in saving_backend.received_commands[7:9]: + assert not cnot2rxx._recognize_cnot2(cmd) + + +def _decomp_gates(eng, cmd): + """ Test that the cmd.gate is a gate of class X """ + if len(cmd.control_qubits) == 1 and isinstance(cmd.gate, X.__class__): + return False + return True + + +# ------------test_decomposition function-------------# +# Creates two engines, correct_eng and test_eng. +# correct_eng implements CNOT gate. +# test_eng implements the decomposition of the CNOT gate. +# correct_qb and test_qb represent results of these two engines, respectively. +# +# The decomposition in this case only produces the same state as CNOT up to a +# global phase. +# test_vector and correct_vector represent the final wave states of correct_qb +# and test_qb. +# +# The dot product of correct_vector and test_vector should have absolute value +# 1, if the two vectors are the same up to a global phase. + + +def test_decomposition(): + """ Test that this decomposition of CNOT produces correct amplitudes + + Function tests each DecompositionRule in + cnot2rxx.all_defined_decomposition_rules + """ + decomposition_rule_list = cnot2rxx.all_defined_decomposition_rules + for rule in decomposition_rule_list: + for basis_state_index in range(0, 4): + basis_state = [0] * 4 + basis_state[basis_state_index] = 1. + correct_dummy_eng = DummyEngine(save_commands=True) + correct_eng = MainEngine(backend=Simulator(), + engine_list=[correct_dummy_eng]) + rule_set = DecompositionRuleSet(rules=[rule]) + test_dummy_eng = DummyEngine(save_commands=True) + test_eng = MainEngine(backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(_decomp_gates), + test_dummy_eng + ]) + test_sim = test_eng.backend + correct_sim = correct_eng.backend + correct_qb = correct_eng.allocate_qubit() + correct_ctrl_qb = correct_eng.allocate_qubit() + correct_eng.flush() + test_qb = test_eng.allocate_qubit() + test_ctrl_qb = test_eng.allocate_qubit() + test_eng.flush() + + correct_sim.set_wavefunction(basis_state, + correct_qb + correct_ctrl_qb) + test_sim.set_wavefunction(basis_state, test_qb + test_ctrl_qb) + CNOT | (test_ctrl_qb, test_qb) + CNOT | (correct_ctrl_qb, correct_qb) + + test_eng.flush() + correct_eng.flush() + + assert len(correct_dummy_eng.received_commands) == 5 + assert len(test_dummy_eng.received_commands) == 10 + + assert correct_eng.backend.cheat()[1] == pytest.approx( + test_eng.backend.cheat()[1], rel=1e-12, abs=1e-12) + + All(Measure) | test_qb + test_ctrl_qb + All(Measure) | correct_qb + correct_ctrl_qb + test_eng.flush(deallocate_qubits=True) + correct_eng.flush(deallocate_qubits=True) diff --git a/projectq/setups/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py new file mode 100644 index 000000000..b54533bad --- /dev/null +++ b/projectq/setups/decompositions/h2rx.py @@ -0,0 +1,57 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Module uses ideas from "Basic circuit compilation techniques for an +# ion-trap quantum machine" by Dmitri Maslov (2017) at +# https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 +""" +Registers a decomposition for the H gate into an Ry and Rx gate. +""" + +import math + +from projectq.cengines import DecompositionRule +from projectq.meta import get_control_count +from projectq.ops import Ph, Rx, Ry, H + + +def _decompose_h2rx_M(cmd): + """ Decompose the Ry gate.""" + # Labelled 'M' for 'minus' because decomposition ends with a Ry(-pi/2) + qubit = cmd.qubits[0] + Rx(math.pi) | qubit + Ph(math.pi/2) | qubit + Ry(-1 * math.pi / 2) | qubit + + +def _decompose_h2rx_N(cmd): + """ Decompose the Ry gate.""" + # Labelled 'N' for 'neutral' because decomposition doesn't end with + # Ry(pi/2) or Ry(-pi/2) + qubit = cmd.qubits[0] + Ry(math.pi / 2) | qubit + Ph(3*math.pi/2) | qubit + Rx(-1 * math.pi) | qubit + + +def _recognize_HNoCtrl(cmd): + """ For efficiency reasons only if no control qubits.""" + return get_control_count(cmd) == 0 + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(H.__class__, _decompose_h2rx_N, _recognize_HNoCtrl), + DecompositionRule(H.__class__, _decompose_h2rx_M, _recognize_HNoCtrl) +] diff --git a/projectq/setups/decompositions/h2rx_test.py b/projectq/setups/decompositions/h2rx_test.py new file mode 100644 index 000000000..2df048801 --- /dev/null +++ b/projectq/setups/decompositions/h2rx_test.py @@ -0,0 +1,117 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Tests for projectq.setups.decompositions.h2rx.py" + +import numpy as np + +import pytest + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, + InstructionFilter) +from projectq.meta import Control +from projectq.ops import Measure, X, H, HGate + +from . import h2rx + + +def test_recognize_correct_gates(): + """ Test that recognize_HNoCtrl recognizes ctrl qubits """ + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend) + qubit = eng.allocate_qubit() + ctrl_qubit = eng.allocate_qubit() + eng.flush() + H | qubit + with Control(eng, ctrl_qubit): + H | qubit + eng.flush(deallocate_qubits=True) + assert h2rx._recognize_HNoCtrl(saving_backend.received_commands[3]) + assert not h2rx._recognize_HNoCtrl(saving_backend.received_commands[4]) + + +def h_decomp_gates(eng, cmd): + """ Test that cmd.gate is a gate of class HGate """ + g = cmd.gate + if isinstance(g, HGate): # H is just a shortcut to HGate + return False + else: + return True + + +# ------------test_decomposition function-------------# +# Creates two engines, correct_eng and test_eng. +# correct_eng implements H gate. +# test_eng implements the decomposition of the H gate. +# correct_qb and test_qb represent results of these two engines, respectively. +# +# The decomposition in this case only produces the same state as H up to a +# global phase. +# test_vector and correct_vector represent the final wave states of correct_qb +# and test_qb. +# The dot product of correct_vector and test_vector should have absolute value +# 1, if the two vectors are the same up to a global phase. + + +def test_decomposition(): + """ Test that this decomposition of H produces correct amplitudes + + Function tests each DecompositionRule in + h2rx.all_defined_decomposition_rules + """ + decomposition_rule_list = h2rx.all_defined_decomposition_rules + for rule in decomposition_rule_list: + for basis_state_index in range(2): + basis_state = [0] * 2 + basis_state[basis_state_index] = 1. + + correct_dummy_eng = DummyEngine(save_commands=True) + correct_eng = MainEngine(backend=Simulator(), + engine_list=[correct_dummy_eng]) + + rule_set = DecompositionRuleSet(rules=[rule]) + test_dummy_eng = DummyEngine(save_commands=True) + test_eng = MainEngine(backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(h_decomp_gates), + test_dummy_eng + ]) + + correct_qb = correct_eng.allocate_qubit() + correct_eng.flush() + test_qb = test_eng.allocate_qubit() + test_eng.flush() + + correct_eng.backend.set_wavefunction(basis_state, correct_qb) + test_eng.backend.set_wavefunction(basis_state, test_qb) + + H | correct_qb + H | test_qb + + correct_eng.flush() + test_eng.flush() + + assert H in (cmd.gate + for cmd in correct_dummy_eng.received_commands) + assert H not in (cmd.gate + for cmd in test_dummy_eng.received_commands) + + assert correct_eng.backend.cheat()[1] == pytest.approx( + test_eng.backend.cheat()[1], rel=1e-12, abs=1e-12) + + Measure | test_qb + Measure | correct_qb diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py new file mode 100644 index 000000000..f49ba72e1 --- /dev/null +++ b/projectq/setups/decompositions/rz2rx.py @@ -0,0 +1,67 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Module uses ideas from "Basic circuit compilation techniques for an +# ion-trap quantum machine" by Dmitri Maslov (2017) at +# https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 +""" +Registers a decomposition for the Rz gate into an Rx and Ry(pi/2) or Ry(-pi/2) +gate +""" + +import math + +from projectq.cengines import DecompositionRule +from projectq.meta import Compute, Control, get_control_count, Uncompute +from projectq.ops import Rx, Ry, Rz + + +def _decompose_rz2rx_P(cmd): + """ Decompose the Rz using negative angle. """ + # Labelled 'P' for 'plus' because decomposition ends with a Ry(+pi/2) + qubit = cmd.qubits[0] + eng = cmd.engine + angle = cmd.gate.angle + + with Control(eng, cmd.control_qubits): + with Compute(eng): + Ry(-math.pi / 2.) | qubit + Rx(-angle) | qubit + Uncompute(eng) + + +def _decompose_rz2rx_M(cmd): + """ Decompose the Rz using positive angle. """ + # Labelled 'M' for 'minus' because decomposition ends with a Ry(-pi/2) + qubit = cmd.qubits[0] + eng = cmd.engine + angle = cmd.gate.angle + + with Control(eng, cmd.control_qubits): + with Compute(eng): + Ry(math.pi / 2.) | qubit + Rx(angle) | qubit + Uncompute(eng) + + +def _recognize_RzNoCtrl(cmd): + """ Decompose the gate only if the command represents a single qubit gate (if it is not part of a control gate).""" + return get_control_count(cmd) == 0 + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(Rz, _decompose_rz2rx_P, _recognize_RzNoCtrl), + DecompositionRule(Rz, _decompose_rz2rx_M, _recognize_RzNoCtrl) +] diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py new file mode 100644 index 000000000..7c6c9962f --- /dev/null +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -0,0 +1,125 @@ +# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Tests for projectq.setups.decompositions.rz2rx.py" + +import math +import numpy as np +import pytest + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, + InstructionFilter) +from projectq.meta import Control +from projectq.ops import Measure, Rz + +from . import rz2rx + + +def test_recognize_correct_gates(): + """ Test that recognize_RzNoCtrl recognizes ctrl qubits """ + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend) + qubit = eng.allocate_qubit() + ctrl_qubit = eng.allocate_qubit() + eng.flush() + Rz(0.3) | qubit + with Control(eng, ctrl_qubit): + Rz(0.4) | qubit + eng.flush(deallocate_qubits=True) + assert rz2rx._recognize_RzNoCtrl(saving_backend.received_commands[3]) + assert not rz2rx._recognize_RzNoCtrl(saving_backend.received_commands[4]) + + +def rz_decomp_gates(eng, cmd): + """ Test that cmd.gate is the gate Rz """ + g = cmd.gate + if isinstance(g, Rz): + return False + else: + return True + + +# ------------test_decomposition function-------------# +# Creates two engines, correct_eng and test_eng. +# correct_eng implements Rz(angle) gate. +# test_eng implements the decomposition of the Rz(angle) gate. +# correct_qb and test_qb represent results of these two engines, respectively. +# +# The decomposition only needs to produce the same state in a qubit up to a +# global phase. +# test_vector and correct_vector represent the final wave states of correct_qb +# and test_qb. +# +# The dot product of correct_vector and test_vector should have absolute value +# 1, if the two vectors are the same up to a global phase. + + +@pytest.mark.parametrize("angle", [0, math.pi, 2 * math.pi, 4 * math.pi, 0.5]) +def test_decomposition(angle): + """ + Test that this decomposition of Rz produces correct amplitudes + + Note that this function tests each DecompositionRule in + rz2rx.all_defined_decomposition_rules + """ + decomposition_rule_list = rz2rx.all_defined_decomposition_rules + for rule in decomposition_rule_list: + for basis_state in ([1, 0], [0, 1]): + correct_dummy_eng = DummyEngine(save_commands=True) + correct_eng = MainEngine(backend=Simulator(), + engine_list=[correct_dummy_eng]) + + rule_set = DecompositionRuleSet(rules=[rule]) + test_dummy_eng = DummyEngine(save_commands=True) + test_eng = MainEngine(backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(rz_decomp_gates), + test_dummy_eng + ]) + + correct_qb = correct_eng.allocate_qubit() + Rz(angle) | correct_qb + correct_eng.flush() + + test_qb = test_eng.allocate_qubit() + Rz(angle) | test_qb + test_eng.flush() + + # Create empty vectors for the wave vectors for the correct and + # test qubits + correct_vector = np.zeros((2, 1), dtype=np.complex_) + test_vector = np.zeros((2, 1), dtype=np.complex_) + + i = 0 + for fstate in ['0', '1']: + test = test_eng.backend.get_amplitude(fstate, test_qb) + correct = correct_eng.backend.get_amplitude(fstate, correct_qb) + correct_vector[i] = correct + test_vector[i] = test + i += 1 + + # Necessary to transpose vector to use matrix dot product + test_vector = test_vector.transpose() + # Remember that transposed vector should come first in product + vector_dot_product = np.dot(test_vector, correct_vector) + + assert np.absolute(vector_dot_product) == pytest.approx(1, + rel=1e-12, + abs=1e-12) + + Measure | test_qb + Measure | correct_qb diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index 7b3540cf0..fe0c00ba2 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Defines a setup to compile to a restricted gate set. @@ -27,8 +26,7 @@ import projectq.libs.math import projectq.setups.decompositions from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - InstructionFilter, LocalOptimizer, - TagRemover) + InstructionFilter, LocalOptimizer, TagRemover) from projectq.ops import (BasicGate, BasicMathGate, ClassicalInstructionGate, CNOT, ControlledGate, get_inverse, QFT, Swap) @@ -60,9 +58,14 @@ def one_and_two_qubit_gates(eng, cmd): return False +def default_chooser(cmd, decomposition_list): + return decomposition_list[0] + + def get_engine_list(one_qubit_gates="any", - two_qubit_gates=(CNOT,), - other_gates=()): + two_qubit_gates=(CNOT, ), + other_gates=(), + compiler_chooser=default_chooser): """ Returns an engine list to compile to a restricted gate set. @@ -73,8 +76,8 @@ def get_engine_list(one_qubit_gates="any", even the gate sets which work might not yet be optimized. So make sure to double check and potentially extend the decomposition rules. This implemention currently requires that the one qubit gates must - contain Rz and at least one of {Ry(best), Rx, H} and the two qubit gate - must contain CNOT (recommended) or CZ. + contain Rz and at least one of {Ry(best), Rx, H} and the two qubit + gate must contain CNOT (recommended) or CZ. Note: Classical instructions gates such as e.g. Flush and Measure are @@ -86,21 +89,24 @@ def get_engine_list(one_qubit_gates="any", other_gates=(TimeEvolution,)) Args: - one_qubit_gates: "any" allows any one qubit gate, otherwise provide - a tuple of the allowed gates. If the gates are + one_qubit_gates: "any" allows any one qubit gate, otherwise provide a + tuple of the allowed gates. If the gates are instances of a class (e.g. X), it allows all gates - which are equal to it. If the gate is a class (Rz), it - allows all instances of this class. Default is "any" - two_qubit_gates: "any" allows any two qubit gate, otherwise provide - a tuple of the allowed gates. If the gates are + which are equal to it. If the gate is a class (Rz), + it allows all instances of this class. Default is + "any" + two_qubit_gates: "any" allows any two qubit gate, otherwise provide a + tuple of the allowed gates. If the gates are instances of a class (e.g. CNOT), it allows all gates which are equal to it. If the gate is a class, it allows all instances of this class. Default is (CNOT,). other_gates: A tuple of the allowed gates. If the gates are - instances of a class (e.g. QFT), it allows - all gates which are equal to it. If the gate is a - class, it allows all instances of this class. + instances of a class (e.g. QFT), it allows all gates + which are equal to it. If the gate is a class, it + allows all instances of this class. + compiler_chooser:function selecting the decomposition to use in the + Autoreplacer engine Raises: TypeError: If input is for the gates is not "any" or a tuple. Also if element within tuple is not a class or instance of BasicGate @@ -119,8 +125,8 @@ def get_engine_list(one_qubit_gates="any", if not isinstance(other_gates, tuple): raise TypeError("other_gates parameter must be a tuple.") - rule_set = DecompositionRuleSet(modules=[projectq.libs.math, - projectq.setups.decompositions]) + rule_set = DecompositionRuleSet( + modules=[projectq.libs.math, projectq.setups.decompositions]) allowed_gate_classes = [] # n-qubit gates allowed_gate_instances = [] allowed_gate_classes1 = [] # 1-qubit gates @@ -192,20 +198,21 @@ def low_level_gates(eng, cmd): elif cmd.gate in allowed_gate_instances1 and len(all_qubits) == 1: return True elif ((cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances2 - and len(all_qubits) == 2): + and len(all_qubits) == 2): return True return False - return [AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(high_level_gates), - LocalOptimizer(5), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(one_and_two_qubit_gates), - LocalOptimizer(5), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(low_level_gates), - LocalOptimizer(5), - ] + return [ + AutoReplacer(rule_set, compiler_chooser), + TagRemover(), + InstructionFilter(high_level_gates), + LocalOptimizer(5), + AutoReplacer(rule_set, compiler_chooser), + TagRemover(), + InstructionFilter(one_and_two_qubit_gates), + LocalOptimizer(5), + AutoReplacer(rule_set, compiler_chooser), + TagRemover(), + InstructionFilter(low_level_gates), + LocalOptimizer(5), + ] diff --git a/projectq/setups/restrictedgateset_test.py b/projectq/setups/restrictedgateset_test.py index 544746f85..fe9754aa7 100644 --- a/projectq/setups/restrictedgateset_test.py +++ b/projectq/setups/restrictedgateset_test.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.setups.restrictedgateset.""" import pytest @@ -89,14 +88,14 @@ def test_restriction(): def test_wrong_init(): with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(two_qubit_gates=(CNOT)) + restrictedgateset.get_engine_list(two_qubit_gates=(CNOT)) with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(one_qubit_gates="Any") + restrictedgateset.get_engine_list(one_qubit_gates="Any") with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(other_gates="any") + restrictedgateset.get_engine_list(other_gates="any") with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(one_qubit_gates=(CRz,)) + restrictedgateset.get_engine_list(one_qubit_gates=(CRz, )) with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(two_qubit_gates=(CRz,)) + restrictedgateset.get_engine_list(two_qubit_gates=(CRz, )) with pytest.raises(TypeError): - engine_list = restrictedgateset.get_engine_list(other_gates=(CRz,)) + restrictedgateset.get_engine_list(other_gates=(CRz, )) diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py new file mode 100644 index 000000000..f5d19f1c8 --- /dev/null +++ b/projectq/setups/trapped_ion_decomposer.py @@ -0,0 +1,148 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Module uses ideas from "Basic circuit compilation techniques +# for an ion-trap quantum machine" by Dmitri Maslov (2017) at +# https://iopscience.iop.org/article/10.1088/1367-2630/aa5e47 +""" +Apply the restricted gate set setup for trapped ion based quantum computers. + +It provides the `engine_list` for the `MainEngine`, restricting the gate set to +Rx and Ry single qubit gates and the Rxx two qubit gates. + +A decomposition chooser is implemented following the ideas in QUOTE for +reducing the number of Ry gates in the new circuit. + +NOTE: + +Because the decomposition chooser is only called when a gate has to be +decomposed, this reduction will work better when the entire circuit has to be +decomposed. Otherwise, If the circuit has both superconding gates and native +ion trapped gates the decomposed circuit will not be optimal. +""" + +from projectq.setups import restrictedgateset +from projectq.ops import (Rxx, Rx, Ry) +from projectq.meta import get_control_count + +# ------------------chooser_Ry_reducer-------------------# +# If the qubit is not in the prev_Ry_sign dictionary, then no decomposition +# occured +# If the value is: +# -1 then the last gate applied (during a decomposition!) was Ry(-math.pi/2) +# 1 then the last gate applied (during a decomposition!) was Ry(+math.pi/2) +# 0 then the last gate applied (during a decomposition!) was a Rx + +prev_Ry_sign = dict() # Keeps track of most recent Ry sign, i.e. +# whether we had Ry(-pi/2) or Ry(pi/2) +# prev_Ry_sign[qubit_index] should hold -1 or +# +1 + + +def chooser_Ry_reducer(cmd, decomposition_list): + """ + Choose the decomposition so as to maximise Ry cancellations, based on the + previous decomposition used for the given qubit. + + Note: + Classical instructions gates e.g. Flush and Measure are automatically + allowed. + + Returns: + A decomposition object from the decomposition_list. + """ + decomp_rule = dict() + name = 'default' + + for decomp in decomposition_list: + try: + # NB: need to (possibly) raise an exception before setting the + # name variable below + decomposition = decomp.decompose.__name__.split('_') + decomp_rule[decomposition[3]] = decomp + name = decomposition[2] + # 'M' stands for minus, 'P' stands for plus 'N' stands for neutral + # e.g. decomp_rule['M'] will give you the decomposition_rule that + # ends with a Ry(-pi/2) + except IndexError: + pass + + local_prev_Ry_sign = prev_Ry_sign.setdefault(cmd.engine, dict()) + + if name == 'cnot2rxx': + assert get_control_count(cmd) == 1 + ctrl_id = cmd.control_qubits[0].id + + if local_prev_Ry_sign.get(ctrl_id, -1) <= 0: + # If the previous qubit had Ry(-pi/2) choose the decomposition + # that starts with Ry(pi/2) + local_prev_Ry_sign[ctrl_id] = -1 + # Now the prev_Ry_sign is set to -1 since at the end of the + # decomposition we will have a Ry(-pi/2) + return decomp_rule['M'] + + # Previous qubit had Ry(pi/2) choose decomposition that starts + # with Ry(-pi/2) and ends with R(pi/2) + local_prev_Ry_sign[ctrl_id] = 1 + return decomp_rule['P'] + + if name == 'h2rx': + qubit_id = [qb.id for qureg in cmd.qubits for qb in qureg] + assert len(qubit_id) == 1 # this should be a single qubit gate + qubit_id = qubit_id[0] + + if local_prev_Ry_sign.get(qubit_id, 0) == 0: + local_prev_Ry_sign[qubit_id] = 1 + return decomp_rule['M'] + + local_prev_Ry_sign[qubit_id] = 0 + return decomp_rule['N'] + + if name == 'rz2rx': + qubit_id = [qb.id for qureg in cmd.qubits for qb in qureg] + assert len(qubit_id) == 1 # this should be a single qubit gate + qubit_id = qubit_id[0] + + if local_prev_Ry_sign.get(qubit_id, -1) <= 0: + local_prev_Ry_sign[qubit_id] = -1 + return decomp_rule['M'] + + local_prev_Ry_sign[qubit_id] = 1 + return decomp_rule['P'] + + # No decomposition chosen, so use the first decompostion in the list + # like the default function + return decomposition_list[0] + + +def get_engine_list(): + """ + Returns an engine list compiling code into a trapped ion based compiled + circuit code. + + Note: + + - Classical instructions gates such as e.g. Flush and Measure are + automatically allowed. + - The restricted gate set engine does not work with Rxx gates, as + ProjectQ will by default bounce back and forth between Cz gates and Cx + gates. An appropriate decomposition chooser needs to be used! + + Returns: + A list of suitable compiler engines. + """ + return restrictedgateset.get_engine_list( + one_qubit_gates=(Rx, Ry), + two_qubit_gates=(Rxx, ), + compiler_chooser=chooser_Ry_reducer) diff --git a/projectq/setups/trapped_ion_decomposer_test.py b/projectq/setups/trapped_ion_decomposer_test.py new file mode 100644 index 000000000..23b6485c6 --- /dev/null +++ b/projectq/setups/trapped_ion_decomposer_test.py @@ -0,0 +1,150 @@ +# Copyright 2018 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"Tests for projectq.setups.trapped_ion_decomposer.py." + +import projectq +from projectq.ops import (Rx, Ry, Rz, H, X, CNOT, Measure, Rxx, + ClassicalInstructionGate) +from projectq.cengines import (MainEngine, DummyEngine, AutoReplacer, + TagRemover, InstructionFilter, + DecompositionRuleSet, DecompositionRule) +from projectq.meta import get_control_count + +from . import restrictedgateset +from .trapped_ion_decomposer import chooser_Ry_reducer, get_engine_list + + +def filter_gates(eng, cmd): + if isinstance(cmd.gate, ClassicalInstructionGate): + return True + if ((cmd.gate == X and get_control_count(cmd) == 1) or cmd.gate == H + or isinstance(cmd.gate, Rz)): + return False + return True + + +def test_chooser_Ry_reducer_synthetic(): + backend = DummyEngine(save_commands=True) + rule_set = DecompositionRuleSet( + modules=[projectq.libs.math, projectq.setups.decompositions]) + + engine_list = [ + AutoReplacer(rule_set, chooser_Ry_reducer), + TagRemover(), + InstructionFilter(filter_gates), + ] + + eng = MainEngine(backend=backend, engine_list=engine_list) + control = eng.allocate_qubit() + target = eng.allocate_qubit() + CNOT | (control, target) + CNOT | (control, target) + eng.flush() + idx0 = len(backend.received_commands) - 2 + idx1 = len(backend.received_commands) + CNOT | (control, target) + eng.flush() + + assert isinstance(backend.received_commands[idx0].gate, Ry) + assert isinstance(backend.received_commands[idx1].gate, Ry) + assert (backend.received_commands[idx0].gate.get_inverse() == + backend.received_commands[idx1].gate) + + eng = MainEngine(backend=backend, engine_list=engine_list) + control = eng.allocate_qubit() + target = eng.allocate_qubit() + H | target + eng.flush() + idx0 = len(backend.received_commands) - 2 + idx1 = len(backend.received_commands) + H | target + eng.flush() + + assert isinstance(backend.received_commands[idx0].gate, Ry) + assert isinstance(backend.received_commands[idx1].gate, Ry) + assert (backend.received_commands[idx0].gate.get_inverse() == + backend.received_commands[idx1].gate) + + eng = MainEngine(backend=backend, engine_list=engine_list) + control = eng.allocate_qubit() + target = eng.allocate_qubit() + Rz(1.23456) | target + eng.flush() + idx0 = len(backend.received_commands) - 2 + idx1 = len(backend.received_commands) + Rz(1.23456) | target + eng.flush() + + assert isinstance(backend.received_commands[idx0].gate, Ry) + assert isinstance(backend.received_commands[idx1].gate, Ry) + assert (backend.received_commands[idx0].gate.get_inverse() == + backend.received_commands[idx1].gate) + + +def _dummy_h2nothing_A(cmd): + qubit = cmd.qubits[0] + Ry(1.23456) | qubit + + +def test_chooser_Ry_reducer_unsupported_gate(): + backend = DummyEngine(save_commands=True) + rule_set = DecompositionRuleSet( + rules=[DecompositionRule(H.__class__, _dummy_h2nothing_A)]) + + engine_list = [ + AutoReplacer(rule_set, chooser_Ry_reducer), + TagRemover(), + InstructionFilter(filter_gates), + ] + + eng = MainEngine(backend=backend, engine_list=engine_list) + qubit = eng.allocate_qubit() + H | qubit + eng.flush() + + for cmd in backend.received_commands: + print(cmd) + + assert isinstance(backend.received_commands[1].gate, Ry) + + +def test_chooser_Ry_reducer(): + # Without the chooser_Ry_reducer function, i.e. if the restricted gate set + # just picked the first option in each decomposition list, the circuit + # below would be decomposed into 8 single qubit gates and 1 two qubit + # gate. + # + # Including the Allocate, Measure and Flush commands, this would result in + # 13 commands. + # + # Using the chooser_Rx_reducer you get 10 commands, since you now have 4 + # single qubit gates and 1 two qubit gate. + + for engine_list, count in [(restrictedgateset.get_engine_list( + one_qubit_gates=(Rx, Ry), + two_qubit_gates=(Rxx, )), 13), + (get_engine_list(), 11)]: + + backend = DummyEngine(save_commands=True) + eng = projectq.MainEngine(backend, engine_list, verbose=True) + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + H | qubit1 + CNOT | (qubit1, qubit2) + Rz(0.2) | qubit1 + Measure | qubit1 + eng.flush() + + assert len(backend.received_commands) == count