From 853954d7ed559b4f8c0296c1c732186f0e0f00fa Mon Sep 17 00:00:00 2001 From: "Damian S. Steiger" Date: Sat, 25 Aug 2018 11:32:46 +0200 Subject: [PATCH 1/8] allow to apply unitary qubit operators to qubits --- projectq/backends/_sim/_simulator.py | 4 +- .../libs/revkit/_control_function_test.py | 1 + projectq/ops/_qubit_operator.py | 164 +++++++++++++++++- projectq/ops/_qubit_operator_test.py | 101 +++++++++++ projectq/setups/decompositions/__init__.py | 2 + 5 files changed, 269 insertions(+), 3 deletions(-) diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index 500c0954d..2218c3471 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -303,8 +303,8 @@ def collapse_wavefunction(self, qureg, values): Args: qureg (Qureg|list[Qubit]): Qubits to collapse. - values (list[bool|int]|string[0|1]): Measurement outcome for each of the qubits - in `qureg`. + values (list[bool|int]|string[0|1]): Measurement outcome for each + of the qubits in `qureg`. Raises: RuntimeError: If an outcome has probability (approximately) 0 or diff --git a/projectq/libs/revkit/_control_function_test.py b/projectq/libs/revkit/_control_function_test.py index 36164a0b7..84ee3cc41 100644 --- a/projectq/libs/revkit/_control_function_test.py +++ b/projectq/libs/revkit/_control_function_test.py @@ -41,6 +41,7 @@ def test_control_function_majority(): assert len(saving_backend.received_commands) == 7 + def test_control_function_majority_from_python(): dormouse = pytest.importorskip('dormouse') diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index c38bd2f57..53b46f3cf 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -13,11 +13,15 @@ # limitations under the License. """QubitOperator stores a sum of Pauli operators acting on qubits.""" +import cmath import copy import itertools import numpy +from projectq.ops import (apply_command, BasicGate, NotInvertible, + NotMergeable, Ph, X, Y, Z) + EQ_TOLERANCE = 1e-12 @@ -45,7 +49,7 @@ class QubitOperatorError(Exception): pass -class QubitOperator(object): +class QubitOperator(BasicGate): """ A sum of terms acting on qubits, e.g., 0.5 * 'X0 X5' + 0.3 * 'Z1 Z2'. @@ -68,6 +72,25 @@ class QubitOperator(object): hamiltonian = 0.5 * QubitOperator('X0 X5') + 0.3 * QubitOperator('Z0') + Our Simulator takes a hermitian QubitOperator to directly calculate the + expectation value (see Simulator.get_expectation_value) of this observable. + + A hermitian QubitOperator can also be used as input for the + TimeEvolution gate. + + If the QubitOperator is unitary, i.e., it contains only one term with a + coefficient, whose absolute value is 1, then one can apply directly to + qubits: + + .. code-block:: python + + eng = projectq.MainEngine() + qureg = eng.allocate_qureg(6) + QubitOperator('X0 X5', 1.j) | qureg # Applies X to qubit 0 and 5 + # with an additional global phase + # of 1.j + + Attributes: terms (dict): **key**: A term represented by a tuple containing all non-trivial local Pauli operators ('X', 'Y', or 'Z'). @@ -128,6 +151,7 @@ def __init__(self, term=None, coefficient=1.): Raises: QubitOperatorError: Invalid operators provided to QubitOperator. """ + BasicGate.__init__(self) if not isinstance(coefficient, (int, float, complex)): raise ValueError('Coefficient must be a numeric type.') self.terms = {} @@ -226,6 +250,141 @@ def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12): return False return True + def __or__(self, qubits): + """ + Operator| overload which enables the following syntax: + + .. code-block:: python + + QubitOperator(...) | qureg + QubitOperator(...) | (qureg,) + QubitOperator(...) | qubit + QubitOperator(...) | (qubit,) + + Unlike other gates, this gate is only allowed to be applied to one + quantum register or one qubit and only if the QubitOperator is + unitary, i.e., consists of one term with a coefficient whose absolute + values is 1. + + Example: + + .. code-block:: python + + eng = projectq.MainEngine() + qureg = eng.allocate_qureg(6) + QubitOperator('X0 X5', 1.j) | qureg # Applies X to qubit 0 and 5 + # with an additional global phase + # of 1.j + + While in the above example the QubitOperator gate is applied to 6 + qubits, it only acts non-trivially on the two qubits qureg[0] and + qureg[5]. Therefore, the operator| will create a new rescaled + QubitOperator, i.e, it sends the equivalent of the following new gate + to the MainEngine: + + .. code-block:: python + + QubitOperator('X0 X1', 1.j) | [qureg[0], qureg[5]] + + which is only a two qubit gate. + + Args: + qubits: one Qubit object, one list of Qubit objects, one Qureg + object, or a tuple of the former three cases. + + Raises: + TypeError: If QubitOperator is not unitary or applied to more than + one quantum register. + ValueError: If quantum register does not have enough qubits + """ + # Check that input is only one qureg or one qubit + qubits = self.make_tuple_of_qureg(qubits) + if len(qubits) != 1: + raise TypeError("Only one qubit or qureg allowed.") + # Check that operator is unitary + if not len(self.terms) == 1: + raise TypeError("Only unitary QubitOperators can be applied to " + "qubits.") + (term, coefficient), = self.terms.items() + phase = cmath.phase(coefficient) + if (abs(coefficient) < 1 - EQ_TOLERANCE or + abs(coefficient) > 1 + EQ_TOLERANCE): + raise TypeError("Only unitary QubitOperators can be applied to " + "qubits.") + # Test if we need to apply only Ph + if term == (): + Ph(phase) | qubits[0][0] + return + # Check that Qureg has enough qubits: + num_qubits = len(qubits[0]) + non_trivial_qubits = set() + for index, action in term: + non_trivial_qubits.add(index) + if max(non_trivial_qubits) >= num_qubits: + raise ValueError("QubitOperator acts on more qubits than the gate " + "is applied to.") + # Apply X, Y, Z, if QubitOperator acts only on one qubit + if len(term) == 1: + if term[0][1] == "X": + X | qubits[0][term[0][0]] + elif term[0][1] == "Y": + Y | qubits[0][term[0][0]] + elif term[0][1] == "Z": + Z | qubits[0][term[0][0]] + Ph(phase) | qubits[0][term[0][0]] + return + # Create new QubitOperator gate with rescaled qubit indices in + # 0,..., len(non_trivial_qubits) - 1 + new_index = dict() + non_trivial_qubits = sorted(list(non_trivial_qubits)) + for i in range(len(non_trivial_qubits)): + new_index[non_trivial_qubits[i]] = i + new_qubitoperator = QubitOperator() + assert len(new_qubitoperator.terms) == 0 + new_term = tuple([(new_index[index], action) + for index, action in term]) + new_qubitoperator.terms[new_term] = coefficient + new_qubits = [qubits[0][i] for i in non_trivial_qubits] + # Apply new gate + cmd = new_qubitoperator.generate_command(new_qubits) + apply_command(cmd) + + def get_inverse(self): + """ + Return the inverse gate if QubitOperator is unitary. + + Raises: + NotInvertible: inverse is not implemented if not unitary + """ + if len(self.terms) == 1: + (term, coefficient), = self.terms.items() + return QubitOperator(term, coefficient**(-1)) + else: + raise NotInvertible("BasicGate: No get_inverse() implemented.") + + def get_merged(self, other): + """ + Return this gate merged with another gate. + + Standard implementation of get_merged: + + Raises: + NotMergeable: merging is not implemented + """ + if (isinstance(other, self.__class__) and + len(other.terms) == 1 and + len(self.terms) == 1): + return self * other + + # (term, coefficient), = self.terms.items() + # (other_term, other_coefficient), = other.terms.items() + # if term == other_term: + # return self.__class__(term, coefficient * other_coefficient) + # else: + # raise NotMergeable() + else: + raise NotMergeable() + def __imul__(self, multiplier): """ In-place multiply (*=) terms with scalar or QubitOperator. @@ -463,3 +622,6 @@ def __str__(self): def __repr__(self): return str(self) + + def __hash__(self): + return hash(str(self)) diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index 12e1d19dd..82c9ef155 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -13,11 +13,16 @@ # limitations under the License. """Tests for _qubit_operator.py.""" +import cmath import copy +import math import numpy import pytest +from projectq import MainEngine +from projectq.cengines import DummyEngine +from projectq.ops import NotInvertible, NotMergeable, Ph, T, X, Y, Z from projectq.ops import _qubit_operator as qo @@ -184,6 +189,97 @@ def test_isclose_different_num_terms(): assert not a.isclose(b, rel_tol=1e-12, abs_tol=0.05) +def test_get_inverse(): + qo0 = qo.QubitOperator("X1 Z2", 2) + qo1 = qo.QubitOperator("", 2j) + assert qo0.get_inverse().isclose(qo.QubitOperator("X1 Z2", 0.5)) + assert qo1.get_inverse().isclose(qo.QubitOperator("", -0.5j)) + qo0 += qo1 + with pytest.raises(NotInvertible): + qo0.get_inverse() + + +def test_get_merged(): + qo0 = qo.QubitOperator("X1 Z2", 1j) + qo1 = qo.QubitOperator("Y3", 1j) + merged = qo0.get_merged(qo1) + assert qo0.isclose(qo.QubitOperator("X1 Z2", 1j)) + assert qo1.isclose(qo.QubitOperator("Y3", 1j)) + assert qo0.get_merged(qo1).isclose(qo.QubitOperator("X1 Z2 Y3", -1)) + with pytest.raises(NotMergeable): + qo1.get_merged(T) + qo2 = qo0 + qo1 + with pytest.raises(NotMergeable): + qo2.get_merged(qo0) + with pytest.raises(NotMergeable): + qo0.get_merged(qo2) + + +def test_or_one_qubit(): + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend, engine_list=[]) + qureg = eng.allocate_qureg(3) + eng.flush() + identity = qo.QubitOperator("", 1j) + x = qo.QubitOperator("X1", cmath.exp(0.5j)) + y = qo.QubitOperator("Y2", cmath.exp(0.6j)) + z = qo.QubitOperator("Z0", cmath.exp(4.5j)) + identity | qureg + eng.flush() + x | qureg + eng.flush() + y | qureg + eng.flush() + z | qureg + eng.flush() + assert saving_backend.received_commands[4].gate == Ph(math.pi/2.) + + assert saving_backend.received_commands[6].gate == X + assert saving_backend.received_commands[6].qubits == ([qureg[1]],) + assert saving_backend.received_commands[7].gate == Ph(0.5) + assert saving_backend.received_commands[7].qubits == ([qureg[1]],) + + assert saving_backend.received_commands[9].gate == Y + assert saving_backend.received_commands[9].qubits == ([qureg[2]],) + assert saving_backend.received_commands[10].gate == Ph(0.6) + assert saving_backend.received_commands[10].qubits == ([qureg[2]],) + + assert saving_backend.received_commands[12].gate == Z + assert saving_backend.received_commands[12].qubits == ([qureg[0]],) + assert saving_backend.received_commands[13].gate == Ph(4.5) + assert saving_backend.received_commands[13].qubits == ([qureg[0]],) + + +def test_wrong_input(): + eng = MainEngine() + qureg = eng.allocate_qureg(3) + op0 = qo.QubitOperator("X1", 0.99) + with pytest.raises(TypeError): + op0 | qureg + op1 = qo.QubitOperator("X2", 1) + with pytest.raises(ValueError): + op1 | qureg[1] + with pytest.raises(TypeError): + op0 | (qureg[1], qureg[2]) + op2 = op0 + op1 + with pytest.raises(TypeError): + op2 | qureg + + +def test_rescaling_of_indices(): + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend, engine_list=[]) + qureg = eng.allocate_qureg(4) + eng.flush() + op = qo.QubitOperator("X0 Y1 Z3", 1j) + op | qureg + eng.flush() + assert saving_backend.received_commands[5].gate.isclose( + qo.QubitOperator("X0 Y1 Z2", 1j)) + # test that gate creates a new QubitOperator + assert op.isclose(qo.QubitOperator("X0 Y1 Z3", 1j)) + + def test_imul_inplace(): qubit_op = qo.QubitOperator("X1") prev_id = id(qubit_op) @@ -444,6 +540,11 @@ def test_str(): assert str(op2) == "2 I" +def test_hash(): + op = qo.QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) + assert hash(op) == hash("0.5 X1 Y3 Z8") + + def test_str_empty(): op = qo.QubitOperator() assert str(op) == '0' diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index e2f38a7d3..aab71b28c 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -21,6 +21,7 @@ entangle, globalphase, ph2r, + qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, @@ -43,6 +44,7 @@ entangle, globalphase, ph2r, + qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, From 7b0777914eee9ffcf482859625d5124e97ef97f1 Mon Sep 17 00:00:00 2001 From: "Damian S. Steiger" Date: Sat, 25 Aug 2018 11:36:18 +0200 Subject: [PATCH 2/8] typo --- projectq/ops/_qubit_operator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index 53b46f3cf..17ba0c6e1 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -79,7 +79,7 @@ class QubitOperator(BasicGate): TimeEvolution gate. If the QubitOperator is unitary, i.e., it contains only one term with a - coefficient, whose absolute value is 1, then one can apply directly to + coefficient, whose absolute value is 1, then one can apply it directly to qubits: .. code-block:: python From da90661e44a06684a66de319df750526dff73b74 Mon Sep 17 00:00:00 2001 From: "Damian S. Steiger" Date: Sat, 25 Aug 2018 12:37:44 +0200 Subject: [PATCH 3/8] change to relative imports --- projectq/ops/_qubit_operator.py | 5 +++-- projectq/ops/_qubit_operator_test.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index 17ba0c6e1..041dce978 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -19,8 +19,9 @@ import numpy -from projectq.ops import (apply_command, BasicGate, NotInvertible, - NotMergeable, Ph, X, Y, Z) +from ._basics import BasicGate, NotInvertible, NotMergeable +from ._command import apply_command +from ._gates import Ph, X, Y, Z EQ_TOLERANCE = 1e-12 diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index 82c9ef155..9512e86c5 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -22,7 +22,9 @@ from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import NotInvertible, NotMergeable, Ph, T, X, Y, Z +from ._basics import NotInvertible, NotMergeable +from ._gates import Ph, T, X, Y, Z + from projectq.ops import _qubit_operator as qo From b994881d1469d5d4d69df059d1b72a4457389954 Mon Sep 17 00:00:00 2001 From: "Damian S. Steiger" Date: Sat, 25 Aug 2018 13:05:40 +0200 Subject: [PATCH 4/8] Forgot some new files :/ --- .../setups/decompositions/qubitop2onequbit.py | 48 +++++++++ .../decompositions/qubitop2onequbit_test.py | 100 ++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 projectq/setups/decompositions/qubitop2onequbit.py create mode 100644 projectq/setups/decompositions/qubitop2onequbit_test.py diff --git a/projectq/setups/decompositions/qubitop2onequbit.py b/projectq/setups/decompositions/qubitop2onequbit.py new file mode 100644 index 000000000..b66f64b25 --- /dev/null +++ b/projectq/setups/decompositions/qubitop2onequbit.py @@ -0,0 +1,48 @@ +# 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. + +import cmath + +from projectq.cengines import DecompositionRule +from projectq.meta import Control, get_control_count +from projectq.ops import Ph, QubitOperator, X, Y, Z + + +def _recognize_qubitop(cmd): + """ For efficiency only use this if at most 1 control qubit.""" + return get_control_count(cmd) <= 1 + + +def _decompose_qubitop(cmd): + assert len(cmd.qubits) == 1 + qureg = cmd.qubits[0] + eng = cmd.engine + qubit_op = cmd.gate + with Control(eng, cmd.control_qubits): + (term, coefficient), = qubit_op.terms.items() + phase = cmath.phase(coefficient) + Ph(phase) | qureg[0] + for index, action in term: + if action == "X": + X | qureg[index] + elif action == "Y": + Y | qureg[index] + elif action == "Z": + Z | qureg[index] + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(QubitOperator, _decompose_qubitop, _recognize_qubitop) +] diff --git a/projectq/setups/decompositions/qubitop2onequbit_test.py b/projectq/setups/decompositions/qubitop2onequbit_test.py new file mode 100644 index 000000000..3b1741cfb --- /dev/null +++ b/projectq/setups/decompositions/qubitop2onequbit_test.py @@ -0,0 +1,100 @@ +# 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. + +import cmath + +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 All, Measure, Ph, QubitOperator, X, Y, Z + + +import projectq.setups.decompositions.qubitop2onequbit as qubitop2onequbit + + +def test_recognize(): + saving_backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=saving_backend, engine_list=[]) + ctrl_qureg = eng.allocate_qureg(2) + qureg = eng.allocate_qureg(2) + with Control(eng, ctrl_qureg): + QubitOperator("X0 Y1") | qureg + with Control(eng, ctrl_qureg[0]): + QubitOperator("X0 Y1") | qureg + eng.flush() + cmd0 = saving_backend.received_commands[4] + cmd1 = saving_backend.received_commands[5] + assert not qubitop2onequbit._recognize_qubitop(cmd0) + assert qubitop2onequbit._recognize_qubitop(cmd1) + + +def _decomp_gates(eng, cmd): + if isinstance(cmd.gate, QubitOperator): + return False + else: + return True + + +def test_qubitop2singlequbit(): + num_qubits = 4 + random_initial_state = [0.2+0.1*x*cmath.exp(0.1j+0.2j*x) + for x in range(2**(num_qubits+1))] + rule_set = DecompositionRuleSet(modules=[qubitop2onequbit]) + test_eng = MainEngine(backend=Simulator(), + engine_list=[AutoReplacer(rule_set), + InstructionFilter(_decomp_gates)]) + test_qureg = test_eng.allocate_qureg(num_qubits) + test_ctrl_qb = test_eng.allocate_qubit() + test_eng.flush() + test_eng.backend.set_wavefunction(random_initial_state, + test_qureg + test_ctrl_qb) + correct_eng = MainEngine() + correct_qureg = correct_eng.allocate_qureg(num_qubits) + correct_ctrl_qb = correct_eng.allocate_qubit() + correct_eng.flush() + correct_eng.backend.set_wavefunction(random_initial_state, + correct_qureg + correct_ctrl_qb) + + qubit_op_0 = QubitOperator("X0 Y1 Z3", -1.j) + qubit_op_1 = QubitOperator("Z0 Y1 X3", cmath.exp(0.6j)) + + qubit_op_0 | test_qureg + with Control(test_eng, test_ctrl_qb): + qubit_op_1 | test_qureg + test_eng.flush() + + correct_eng.backend.apply_qubit_operator(qubit_op_0, correct_qureg) + with Control(correct_eng, correct_ctrl_qb): + Ph(0.6) | correct_qureg[0] + Z | correct_qureg[0] + Y | correct_qureg[1] + X | correct_qureg[3] + correct_eng.flush() + + for fstate in range(2**(num_qubits+1)): + binary_state = format(fstate, '0' + str(num_qubits+1) + 'b') + test = test_eng.backend.get_amplitude(binary_state, + test_qureg + test_ctrl_qb) + correct = correct_eng.backend.get_amplitude( + binary_state, correct_qureg + correct_ctrl_qb) + assert correct == pytest.approx(test, rel=1e-10, abs=1e-10) + + All(Measure) | correct_qureg + correct_ctrl_qb + All(Measure) | test_qureg + test_ctrl_qb + correct_eng.flush() + test_eng.flush() From d3651a77a4980335860f02d7ae2d8bff8865c1eb Mon Sep 17 00:00:00 2001 From: "Damian S. Steiger" Date: Sun, 26 Aug 2018 09:49:02 +0200 Subject: [PATCH 5/8] addresses comments --- projectq/ops/_qubit_operator.py | 43 ++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index 041dce978..11e28a02d 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -271,11 +271,11 @@ def __or__(self, qubits): .. code-block:: python - eng = projectq.MainEngine() - qureg = eng.allocate_qureg(6) - QubitOperator('X0 X5', 1.j) | qureg # Applies X to qubit 0 and 5 - # with an additional global phase - # of 1.j + eng = projectq.MainEngine() + qureg = eng.allocate_qureg(6) + QubitOperator('X0 X5', 1.j) | qureg # Applies X to qubit 0 and 5 + # with an additional global + # phase of 1.j While in the above example the QubitOperator gate is applied to 6 qubits, it only acts non-trivially on the two qubits qureg[0] and @@ -304,14 +304,19 @@ def __or__(self, qubits): raise TypeError("Only one qubit or qureg allowed.") # Check that operator is unitary if not len(self.terms) == 1: - raise TypeError("Only unitary QubitOperators can be applied to " - "qubits.") + raise TypeError("Too many terms. Only QubitOperators consisting " + "of a single term (single n-qubit Pauli operator) " + "with a coefficient of unit length can be applied " + "to qubits with this function.") (term, coefficient), = self.terms.items() phase = cmath.phase(coefficient) if (abs(coefficient) < 1 - EQ_TOLERANCE or abs(coefficient) > 1 + EQ_TOLERANCE): - raise TypeError("Only unitary QubitOperators can be applied to " - "qubits.") + raise TypeError("abs(coefficient) != 1. Only QubitOperators " + "consisting of a single term (single n-qubit " + "Pauli operator) with a coefficient of unit " + "length can be applied to qubits with this " + "function.") # Test if we need to apply only Ph if term == (): Ph(phase) | qubits[0][0] @@ -352,14 +357,19 @@ def __or__(self, qubits): def get_inverse(self): """ - Return the inverse gate if QubitOperator is unitary. + Return the inverse gate of a QubitOperator if applied as a gate. Raises: - NotInvertible: inverse is not implemented if not unitary + NotInvertible: Not implemented for QubitOperators which have + multiple terms or a coefficient with absolute value + not equal to 1. """ + if len(self.terms) == 1: (term, coefficient), = self.terms.items() - return QubitOperator(term, coefficient**(-1)) + if (not abs(coefficient) < 1 - EQ_TOLERANCE and not + abs(coefficient) > 1 + EQ_TOLERANCE): + return QubitOperator(term, coefficient**(-1)) else: raise NotInvertible("BasicGate: No get_inverse() implemented.") @@ -370,19 +380,12 @@ def get_merged(self, other): Standard implementation of get_merged: Raises: - NotMergeable: merging is not implemented + NotMergeable: merging is not possible """ if (isinstance(other, self.__class__) and len(other.terms) == 1 and len(self.terms) == 1): return self * other - - # (term, coefficient), = self.terms.items() - # (other_term, other_coefficient), = other.terms.items() - # if term == other_term: - # return self.__class__(term, coefficient * other_coefficient) - # else: - # raise NotMergeable() else: raise NotMergeable() From 51d4faa00108f1205967609c46779f7e02d98edb Mon Sep 17 00:00:00 2001 From: "Damian S. Steiger" Date: Sun, 26 Aug 2018 09:55:08 +0200 Subject: [PATCH 6/8] add missing raise --- projectq/ops/_qubit_operator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index 11e28a02d..aa9a29f9a 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -370,8 +370,7 @@ def get_inverse(self): if (not abs(coefficient) < 1 - EQ_TOLERANCE and not abs(coefficient) > 1 + EQ_TOLERANCE): return QubitOperator(term, coefficient**(-1)) - else: - raise NotInvertible("BasicGate: No get_inverse() implemented.") + raise NotInvertible("BasicGate: No get_inverse() implemented.") def get_merged(self, other): """ From b4532a962ec9f38a64825b395724411f11bdb901 Mon Sep 17 00:00:00 2001 From: "Damian S. Steiger" Date: Sun, 26 Aug 2018 10:00:08 +0200 Subject: [PATCH 7/8] fixed test --- projectq/ops/_qubit_operator_test.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index 9512e86c5..76c832bf8 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -192,10 +192,11 @@ def test_isclose_different_num_terms(): def test_get_inverse(): - qo0 = qo.QubitOperator("X1 Z2", 2) - qo1 = qo.QubitOperator("", 2j) - assert qo0.get_inverse().isclose(qo.QubitOperator("X1 Z2", 0.5)) - assert qo1.get_inverse().isclose(qo.QubitOperator("", -0.5j)) + qo0 = qo.QubitOperator("X1 Z2", cmath.exp(0.6j)) + qo1 = qo.QubitOperator("", 1j) + assert qo0.get_inverse().isclose( + qo.QubitOperator("X1 Z2", cmath.exp(-0.6j))) + assert qo1.get_inverse().isclose(qo.QubitOperator("", -1j)) qo0 += qo1 with pytest.raises(NotInvertible): qo0.get_inverse() From e7b86e2a7d43a4385123312b58d2eb7bc21d9ebe Mon Sep 17 00:00:00 2001 From: "Damian S. Steiger" Date: Sun, 26 Aug 2018 10:03:16 +0200 Subject: [PATCH 8/8] found an uncovered line in stateprep and fixed it --- projectq/setups/decompositions/stateprep2cnot_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py index e07cd73cc..81137b169 100644 --- a/projectq/setups/decompositions/stateprep2cnot_test.py +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -35,7 +35,7 @@ def test_wrong_final_state(): cmd = Command(None, StatePreparation([0, 1j]), qubits=([qb0, qb1],)) with pytest.raises(ValueError): stateprep2cnot._decompose_state_preparation(cmd) - cmd2 = Command(None, StatePreparation([0, 0.999j]), qubits=([qb0, qb1],)) + cmd2 = Command(None, StatePreparation([0, 0.999j]), qubits=([qb0],)) with pytest.raises(ValueError): stateprep2cnot._decompose_state_preparation(cmd2)