From a37c182145a7aec7c7639407f66f7a0820b8d061 Mon Sep 17 00:00:00 2001 From: logiciqt Date: Wed, 23 Oct 2019 17:22:12 +0100 Subject: [PATCH 01/43] reduced Ry decomposition : modification of restrictedgateset setup to include compiler chooser, extension of available decompositions, new setup based on restrictedgateset but adapted for trapped ion configurations --- projectq/setups/decompositions/__init__.py | 6 ++ projectq/setups/decompositions/cnot2rxx.py | 52 +++++++++++ projectq/setups/decompositions/h2rx.py | 49 ++++++++++ projectq/setups/decompositions/rz2rx.py | 59 ++++++++++++ projectq/setups/restrictedgateset.py | 13 ++- projectq/setups/trapped_ion_decomposer.py | 104 +++++++++++++++++++++ 6 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 projectq/setups/decompositions/cnot2rxx.py create mode 100644 projectq/setups/decompositions/h2rx.py create mode 100644 projectq/setups/decompositions/rz2rx.py create mode 100644 projectq/setups/trapped_ion_decomposer.py diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index 883f04581..d85b068e4 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -17,15 +17,18 @@ carb1qubit2cnotrzandry, crz2cxandrz, cnot2cz, + cnot2rxx, cnu2toffoliandcu, entangle, globalphase, + h2rx, ph2r, qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, ry2rz, + rz2rx, sqrtswap2cnot, stateprep2cnot, swap2cnot, @@ -42,15 +45,18 @@ carb1qubit2cnotrzandry, crz2cxandrz, cnot2cz, + cnot2rxx, 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..62978f068 --- /dev/null +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -0,0 +1,52 @@ +# 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. + +""" +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 Compute, get_control_count, Uncompute +from projectq.ops import Rxx,Ry,Rx,Rz,X,Y,Z +import math + +def _decompose_cnot2rxx_M(cmd): + """ Decompose CNOT gates. """ + ctrl = cmd.control_qubits + eng = cmd.engine + Ry(math.pi/2) | ctrl[0] + Rx(3*math.pi/2)| ctrl[0] + Rx(3*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 gates. """ + ctrl = cmd.control_qubits + eng = cmd.engine + Ry(-math.pi/2) | ctrl[0] + Rx(3*math.pi/2)| ctrl[0] + Rx(3*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): + #return True + return get_control_count(cmd) == 1 + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(X.__class__, _decompose_cnot2rxx_P, _recognize_cnot2), + DecompositionRule(X.__class__, _decompose_cnot2rxx_M, _recognize_cnot2) +] diff --git a/projectq/setups/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py new file mode 100644 index 000000000..44867bb04 --- /dev/null +++ b/projectq/setups/decompositions/h2rx.py @@ -0,0 +1,49 @@ +# 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. + +""" +Registers a decomposition for the H gate into an Ry and Rx 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, H + + +def _decompose_h_N(cmd): + """ Decompose the Ry gate.""" + qubit = cmd.qubits[0] + eng = cmd.engine + Ry(-1*math.pi/2) | qubit + Rx(math.pi) | qubit + +def _decompose_h_M(cmd): + """ Decompose the Ry gate.""" + qubit = cmd.qubits[0] + eng = cmd.engine + Rx(-1*math.pi) | qubit + Ry(math.pi/2) | 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_h_N, _recognize_HNoCtrl), + DecompositionRule(H.__class__, _decompose_h_M, _recognize_HNoCtrl) +] diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py new file mode 100644 index 000000000..b78077d31 --- /dev/null +++ b/projectq/setups/decompositions/rz2rx.py @@ -0,0 +1,59 @@ +# 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. + +""" +Registers a decomposition for the Ry gate into an Rz and Rx(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, H + + +def _decompose_rz_P(cmd): + """ Decompose the Ry gate.""" + 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_rz_M(cmd): + """ Decompose the Ry gate.""" + 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): + """ For efficiency reasons only if no control qubits.""" + return get_control_count(cmd) == 0 + + +#: Decomposition rules +all_defined_decomposition_rules = [ + DecompositionRule(Rz, _decompose_rz_P, _recognize_RzNoCtrl), + DecompositionRule(Rz, _decompose_rz_M, _recognize_RzNoCtrl) +] diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index 7b3540cf0..8575ccedd 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -59,10 +59,13 @@ def one_and_two_qubit_gates(eng, cmd): else: 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=()): + other_gates=(), + compiler_chooser=default_chooser): """ Returns an engine list to compile to a restricted gate set. @@ -101,6 +104,8 @@ def get_engine_list(one_qubit_gates="any", 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 @@ -196,15 +201,15 @@ def low_level_gates(eng, cmd): return True return False - return [AutoReplacer(rule_set), + return [AutoReplacer(rule_set,compiler_chooser), TagRemover(), InstructionFilter(high_level_gates), LocalOptimizer(5), - AutoReplacer(rule_set), + AutoReplacer(rule_set,compiler_chooser), TagRemover(), InstructionFilter(one_and_two_qubit_gates), LocalOptimizer(5), - AutoReplacer(rule_set), + AutoReplacer(rule_set,compiler_chooser), TagRemover(), InstructionFilter(low_level_gates), LocalOptimizer(5), diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py new file mode 100644 index 000000000..f93513fe5 --- /dev/null +++ b/projectq/setups/trapped_ion_decomposer.py @@ -0,0 +1,104 @@ +# 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. + +""" +Defines a setup to compile to a restricted gate set. + +It provides the `engine_list` for the `MainEngine`. This engine list contains +an AutoReplacer with most of the gate decompositions of ProjectQ, which are +used to decompose a circuit into a restricted gate set (with some limitions +on the choice of gates). +""" + +import inspect + +import projectq +import projectq.libs.math +from projectq.setups import restrictedgateset +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, + InstructionFilter, LocalOptimizer, + TagRemover) +from projectq.ops import (Rxx,Rx,Ry) + +liste=dict() + +def chooser_Ry_reducer(cmd,decomposition_list): + provisory_liste=dict() + name='default' + try: + for el in decomposition_list: + decomposition=el.decompose.__name__.split('_') + name=decomposition[2] + provisory_liste[decomposition[3]]=el + except: + return decomposition_list[0] + if name=='cnot2rxx': + ctrl = cmd.control_qubits + idx=str(ctrl) + if idx in liste: + if liste[idx]<=0: + liste[idx]=-1 + return provisory_liste['M'] + else: + liste[idx]=1 + return provisory_liste['P'] + else: + liste[idx]=-1 + return provisory_liste['M'] + elif name=='h': + qubit = cmd.qubits[0] + idx=str(qubit) + if idx not in liste: + liste[idx]=+1 + return provisory_liste['M'] + elif liste[idx]==0: + liste[idx]=+1 + return provisory_liste['M'] + else: + liste[idx]=00 + return provisory_liste['N'] + elif name=='rz': + qubit = cmd.qubits[0] + idx=str(qubit) + if idx not in liste: + liste[idx]=-1 + return provisory_liste['M'] + elif liste[idx]<=0: + liste[idx]=-1 + return provisory_liste['M'] + else: + liste[idx]=1 + return provisory_liste['P'] + else: + 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. + + 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) + + + + + From 5aab56b7384d60d5ac09e5b08bebf14135b5a006 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Fri, 22 Nov 2019 11:52:59 +0000 Subject: [PATCH 02/43] Addition of test file for the rz2rx decomposition Addition of test file for the rz2rx decomposition and edit to comments in the rz2rx file --- .vscode/settings.json | 3 + projectq/setups/decompositions/rz2rx.py | 6 +- projectq/setups/decompositions/rz2rx_test.py | 82 ++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 projectq/setups/decompositions/rz2rx_test.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..fc4e4a635 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "C:\\Users\\daisy\\Anaconda3\\envs\\py36\\python.exe" +} \ No newline at end of file diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index b78077d31..5c40dfa3f 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -13,7 +13,7 @@ # limitations under the License. """ -Registers a decomposition for the Ry gate into an Rz and Rx(pi/2) gate. +Registers a decomposition for the Rz gate into an Rx and Ry(pi/2) or Ry(-pi/2) gate. """ import math @@ -24,7 +24,7 @@ def _decompose_rz_P(cmd): - """ Decompose the Ry gate.""" + """ Decompose the Rz using negative angle.""" qubit = cmd.qubits[0] eng = cmd.engine angle = cmd.gate.angle @@ -36,7 +36,7 @@ def _decompose_rz_P(cmd): Uncompute(eng) def _decompose_rz_M(cmd): - """ Decompose the Ry gate.""" + """ Decompose the Rz using positive angle.""" qubit = cmd.qubits[0] eng = cmd.engine angle = cmd.gate.angle diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py new file mode 100644 index 000000000..4781da8cb --- /dev/null +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -0,0 +1,82 @@ + +"Tests for projectq.setups.decompositions.rx2rz.py" + +import math + +import pytest + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, + DummyEngine, InstructionFilter, MainEngine) +from projectq.meta import Control +from projectq.ops import Measure, Ph, Rz + +# from . import rz2rx +import rz2rx + +import sys + +sys.path.insert(0, 'C:\\Users\\daisy\\OneDrive\\Documents\\Sussex masters\\Masters project\\projectQ_practice_branch\\ProjectQ') + +def test_recognize_correct_gates(): + """ Check that Rz gate is not part of a two-qubit control + gate """ + # Creates a circuit and checks that there is only + # a ctrl qubit if you create one in the circuit + 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): + """ Check that cmd.gate is the gate Rz """ + g = cmd.gate + if isinstance(g, Rz): + return False + else: + return True + + +@pytest.mark.parametrize("angle", [0, math.pi, 2*math.pi, 4*math.pi, 0.5]) +def test_decomposition(angle): + """ Test whether the decomposition of Rz results in + the same superposition of |0> and |1> as just using Rz """ + 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(modules=[rz2rx]) + 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() + + assert correct_dummy_eng.received_commands[1].gate == Rz(angle) + assert test_dummy_eng.received_commands[1].gate != Rz(angle) + + for fstate in ['0', '1']: + test = test_eng.backend.get_amplitude(fstate, test_qb) + correct = correct_eng.backend.get_amplitude(fstate, correct_qb) + assert correct == pytest.approx(test, rel=1e-12, abs=1e-12) + + Measure | test_qb + Measure | correct_qb \ No newline at end of file From c00546956bb65ebe52646c2af84042ac607f51c6 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Fri, 22 Nov 2019 11:54:01 +0000 Subject: [PATCH 03/43] Revert "Addition of test file for the rz2rx decomposition" This reverts commit 5aab56b7384d60d5ac09e5b08bebf14135b5a006. --- .vscode/settings.json | 3 - projectq/setups/decompositions/rz2rx.py | 6 +- projectq/setups/decompositions/rz2rx_test.py | 82 -------------------- 3 files changed, 3 insertions(+), 88 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 projectq/setups/decompositions/rz2rx_test.py diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index fc4e4a635..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.pythonPath": "C:\\Users\\daisy\\Anaconda3\\envs\\py36\\python.exe" -} \ No newline at end of file diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index 5c40dfa3f..b78077d31 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -13,7 +13,7 @@ # limitations under the License. """ -Registers a decomposition for the Rz gate into an Rx and Ry(pi/2) or Ry(-pi/2) gate. +Registers a decomposition for the Ry gate into an Rz and Rx(pi/2) gate. """ import math @@ -24,7 +24,7 @@ def _decompose_rz_P(cmd): - """ Decompose the Rz using negative angle.""" + """ Decompose the Ry gate.""" qubit = cmd.qubits[0] eng = cmd.engine angle = cmd.gate.angle @@ -36,7 +36,7 @@ def _decompose_rz_P(cmd): Uncompute(eng) def _decompose_rz_M(cmd): - """ Decompose the Rz using positive angle.""" + """ Decompose the Ry gate.""" qubit = cmd.qubits[0] eng = cmd.engine angle = cmd.gate.angle diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py deleted file mode 100644 index 4781da8cb..000000000 --- a/projectq/setups/decompositions/rz2rx_test.py +++ /dev/null @@ -1,82 +0,0 @@ - -"Tests for projectq.setups.decompositions.rx2rz.py" - -import math - -import pytest - -from projectq import MainEngine -from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - DummyEngine, InstructionFilter, MainEngine) -from projectq.meta import Control -from projectq.ops import Measure, Ph, Rz - -# from . import rz2rx -import rz2rx - -import sys - -sys.path.insert(0, 'C:\\Users\\daisy\\OneDrive\\Documents\\Sussex masters\\Masters project\\projectQ_practice_branch\\ProjectQ') - -def test_recognize_correct_gates(): - """ Check that Rz gate is not part of a two-qubit control - gate """ - # Creates a circuit and checks that there is only - # a ctrl qubit if you create one in the circuit - 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): - """ Check that cmd.gate is the gate Rz """ - g = cmd.gate - if isinstance(g, Rz): - return False - else: - return True - - -@pytest.mark.parametrize("angle", [0, math.pi, 2*math.pi, 4*math.pi, 0.5]) -def test_decomposition(angle): - """ Test whether the decomposition of Rz results in - the same superposition of |0> and |1> as just using Rz """ - 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(modules=[rz2rx]) - 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() - - assert correct_dummy_eng.received_commands[1].gate == Rz(angle) - assert test_dummy_eng.received_commands[1].gate != Rz(angle) - - for fstate in ['0', '1']: - test = test_eng.backend.get_amplitude(fstate, test_qb) - correct = correct_eng.backend.get_amplitude(fstate, correct_qb) - assert correct == pytest.approx(test, rel=1e-12, abs=1e-12) - - Measure | test_qb - Measure | correct_qb \ No newline at end of file From 161ceefb0104076eccf29f242372d9b1ff3b883f Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Fri, 22 Nov 2019 11:58:06 +0000 Subject: [PATCH 04/43] Create rz2rx_test file Addition of test file for the rz2rx decomposition --- projectq/setups/decompositions/rz2rx_test.py | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 projectq/setups/decompositions/rz2rx_test.py diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py new file mode 100644 index 000000000..21f8ce42d --- /dev/null +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -0,0 +1,77 @@ + +"Tests for projectq.setups.decompositions.rx2rz.py" + +import math + +import pytest + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, + DummyEngine, InstructionFilter, MainEngine) +from projectq.meta import Control +from projectq.ops import Measure, Ph, Rz + +from . import rz2rx + +def test_recognize_correct_gates(): + """ Check that Rz gate is not part of a two-qubit control + gate """ + # Creates a circuit and checks that there is only + # a ctrl qubit if you create one in the circuit + 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): + """ Check that cmd.gate is the gate Rz """ + g = cmd.gate + if isinstance(g, Rz): + return False + else: + return True + + +@pytest.mark.parametrize("angle", [0, math.pi, 2*math.pi, 4*math.pi, 0.5]) +def test_decomposition(angle): + """ Test whether the decomposition of Rz results in + the same superposition of |0> and |1> as just using Rz """ + 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(modules=[rz2rx]) + 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() + + assert correct_dummy_eng.received_commands[1].gate == Rz(angle) + assert test_dummy_eng.received_commands[1].gate != Rz(angle) + + for fstate in ['0', '1']: + test = test_eng.backend.get_amplitude(fstate, test_qb) + correct = correct_eng.backend.get_amplitude(fstate, correct_qb) + assert correct == pytest.approx(test, rel=1e-12, abs=1e-12) + + Measure | test_qb + Measure | correct_qb \ No newline at end of file From 1de8b0d2ee952ecb23804c6273aa88cd949b5a8c Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Fri, 22 Nov 2019 12:02:26 +0000 Subject: [PATCH 05/43] Update rz2rx.py Update to comments --- projectq/setups/decompositions/rz2rx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index b78077d31..c57c3429f 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -13,7 +13,7 @@ # limitations under the License. """ -Registers a decomposition for the Ry gate into an Rz and Rx(pi/2) gate. +Registers a decomposition for the Rz gate into an Rx and Ry(pi/2) or Ry(-pi/2) gate """ import math @@ -24,7 +24,7 @@ def _decompose_rz_P(cmd): - """ Decompose the Ry gate.""" + """ Decompose the Rz using negative angle. """ qubit = cmd.qubits[0] eng = cmd.engine angle = cmd.gate.angle @@ -36,7 +36,7 @@ def _decompose_rz_P(cmd): Uncompute(eng) def _decompose_rz_M(cmd): - """ Decompose the Ry gate.""" + """ Decompose the Rz using positive angle. """ qubit = cmd.qubits[0] eng = cmd.engine angle = cmd.gate.angle From 7aee5723040511faf5a2e177930fce5b8c95a8a5 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Fri, 22 Nov 2019 17:42:10 +0000 Subject: [PATCH 06/43] Update rz2rx_test.py Update to comments --- .vscode/settings.json | 3 +++ projectq/setups/decompositions/rz2rx_test.py | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..fc4e4a635 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "C:\\Users\\daisy\\Anaconda3\\envs\\py36\\python.exe" +} \ No newline at end of file diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py index 21f8ce42d..0a0f4902e 100644 --- a/projectq/setups/decompositions/rz2rx_test.py +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -15,10 +15,9 @@ from . import rz2rx def test_recognize_correct_gates(): - """ Check that Rz gate is not part of a two-qubit control - gate """ - # Creates a circuit and checks that there is only - # a ctrl qubit if you create one in the circuit + """ Checks that the recognize_RzNoCtrl behaves as it should """ + # Creates a circuit and checks that the recognize_RzNoCtrl + # asserts correctly that there is/isn't a ctrl qubit in a given command saving_backend = DummyEngine(save_commands=True) eng = MainEngine(backend=saving_backend) qubit = eng.allocate_qubit() From 1beffdb8fffa8e1c5104619020f59c9f07abae4e Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Mon, 25 Nov 2019 16:29:16 +0000 Subject: [PATCH 07/43] Minor update: remove accidental file --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index fc4e4a635..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.pythonPath": "C:\\Users\\daisy\\Anaconda3\\envs\\py36\\python.exe" -} \ No newline at end of file From 3ad1128d388040dd5393303c35498fc387bfddc1 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Tue, 26 Nov 2019 17:38:26 +0000 Subject: [PATCH 08/43] Minor update rz2rx.py Corrected an angle. --- projectq/setups/decompositions/rz2rx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index c57c3429f..c64162daa 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -32,7 +32,7 @@ def _decompose_rz_P(cmd): with Control(eng, cmd.control_qubits): with Compute(eng): Ry(-math.pi/2.) | qubit - Rx(angle) | qubit + Rx(-angle) | qubit Uncompute(eng) def _decompose_rz_M(cmd): From 6f71faa6cb611179ac001bd4ac7c95098bc36535 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Tue, 26 Nov 2019 17:39:48 +0000 Subject: [PATCH 09/43] Minor update rz2rx_test.py Edited comments. --- projectq/setups/decompositions/rz2rx_test.py | 32 ++++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py index 0a0f4902e..750bee2fd 100644 --- a/projectq/setups/decompositions/rz2rx_test.py +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -1,5 +1,18 @@ - -"Tests for projectq.setups.decompositions.rx2rz.py" +# 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 @@ -15,9 +28,7 @@ from . import rz2rx def test_recognize_correct_gates(): - """ Checks that the recognize_RzNoCtrl behaves as it should """ - # Creates a circuit and checks that the recognize_RzNoCtrl - # asserts correctly that there is/isn't a ctrl qubit in a given command + """ Test that recognize_RzNoCtrl recognizes ctrl qubits """ saving_backend = DummyEngine(save_commands=True) eng = MainEngine(backend=saving_backend) qubit = eng.allocate_qubit() @@ -32,7 +43,7 @@ def test_recognize_correct_gates(): def rz_decomp_gates(eng, cmd): - """ Check that cmd.gate is the gate Rz """ + """ Test that cmd.gate is the gate Rz """ g = cmd.gate if isinstance(g, Rz): return False @@ -42,8 +53,11 @@ def rz_decomp_gates(eng, cmd): @pytest.mark.parametrize("angle", [0, math.pi, 2*math.pi, 4*math.pi, 0.5]) def test_decomposition(angle): - """ Test whether the decomposition of Rz results in - the same superposition of |0> and |1> as just using Rz """ + """ Test that the decomposition of Rz produces correct amplitudes + + Note that this function tests the first DecompositionRule in + rz2rx.all_defined_decomposition_rules + """ for basis_state in ([1, 0], [0, 1]): correct_dummy_eng = DummyEngine(save_commands=True) correct_eng = MainEngine(backend=Simulator(), @@ -73,4 +87,4 @@ def test_decomposition(angle): assert correct == pytest.approx(test, rel=1e-12, abs=1e-12) Measure | test_qb - Measure | correct_qb \ No newline at end of file + Measure | correct_qb From c29f866e5e59411cfb1b92de48892b15aea267f3 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Thu, 5 Dec 2019 10:22:18 +0000 Subject: [PATCH 10/43] Create h2rx_test.py Updated method for test_decomposition which tests that decomposition produces identical wave-state up to a global phase. --- projectq/setups/decompositions/h2rx_test.py | 119 ++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 projectq/setups/decompositions/h2rx_test.py diff --git a/projectq/setups/decompositions/h2rx_test.py b/projectq/setups/decompositions/h2rx_test.py new file mode 100644 index 000000000..60d67cd98 --- /dev/null +++ b/projectq/setups/decompositions/h2rx_test.py @@ -0,0 +1,119 @@ +# 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 math + +import numpy as np + +import pytest + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, + DummyEngine, InstructionFilter, MainEngine) +from projectq.meta import Control +from projectq.ops import Measure, Ph, H, HGate + +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 the gate H """ + 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 + + Note that this function tests the first DecompositionRule in + h2rx.all_defined_decomposition_rules + """ + 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(modules=[h2rx]) + 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() + H | correct_qb + correct_eng.flush() + + test_qb = test_eng.allocate_qubit() + H | test_qb + test_eng.flush() + + assert correct_dummy_eng.received_commands[1].gate == H + assert test_dummy_eng.received_commands[1].gate != H + + # 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 \ No newline at end of file From ab231deb896d7830a3e30978acc9f4e5d8d9b708 Mon Sep 17 00:00:00 2001 From: logiciqt Date: Fri, 6 Dec 2019 14:13:46 +0000 Subject: [PATCH 11/43] Improvement of the decomposition chooser; Note that basic restricted gate set will fail decomposing into Rxx because of the default chooser --- projectq/cengines/_replacer/_replacer.py | 1 - projectq/setups/decompositions/cnot2rxx.py | 5 +-- projectq/setups/decompositions/h2rx.py | 8 ++-- projectq/setups/decompositions/rz2rx.py | 8 ++-- projectq/setups/trapped_ion_decomposer.py | 52 +++++++++++++++------- 5 files changed, 47 insertions(+), 27 deletions(-) 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/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py index 62978f068..af78bf5f0 100644 --- a/projectq/setups/decompositions/cnot2rxx.py +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -22,7 +22,7 @@ import math def _decompose_cnot2rxx_M(cmd): - """ Decompose CNOT gates. """ + """ Decompose CNOT gate into Rxx gate. """ ctrl = cmd.control_qubits eng = cmd.engine Ry(math.pi/2) | ctrl[0] @@ -32,7 +32,7 @@ def _decompose_cnot2rxx_M(cmd): Ry(-1*math.pi/2)| ctrl[0] def _decompose_cnot2rxx_P(cmd): - """ Decompose CNOT gates. """ + """ Decompose CNOT gate into Rxx gate. """ ctrl = cmd.control_qubits eng = cmd.engine Ry(-math.pi/2) | ctrl[0] @@ -42,7 +42,6 @@ def _decompose_cnot2rxx_P(cmd): Ry(math.pi/2)| ctrl[0] def _recognize_cnot2(cmd): - #return True return get_control_count(cmd) == 1 #: Decomposition rules diff --git a/projectq/setups/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py index 44867bb04..575bc1707 100644 --- a/projectq/setups/decompositions/h2rx.py +++ b/projectq/setups/decompositions/h2rx.py @@ -23,14 +23,14 @@ from projectq.ops import Rx, Ry, Rz, H -def _decompose_h_N(cmd): +def _decompose_h2rx_N(cmd): """ Decompose the Ry gate.""" qubit = cmd.qubits[0] eng = cmd.engine Ry(-1*math.pi/2) | qubit Rx(math.pi) | qubit -def _decompose_h_M(cmd): +def _decompose_h2rx_M(cmd): """ Decompose the Ry gate.""" qubit = cmd.qubits[0] eng = cmd.engine @@ -44,6 +44,6 @@ def _recognize_HNoCtrl(cmd): #: Decomposition rules all_defined_decomposition_rules = [ - DecompositionRule(H.__class__, _decompose_h_N, _recognize_HNoCtrl), - DecompositionRule(H.__class__, _decompose_h_M, _recognize_HNoCtrl) + DecompositionRule(H.__class__, _decompose_h2rx_N, _recognize_HNoCtrl), + DecompositionRule(H.__class__, _decompose_h2rx_M, _recognize_HNoCtrl) ] diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index c64162daa..e58949b8e 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -23,7 +23,7 @@ from projectq.ops import Rx, Ry, Rz, H -def _decompose_rz_P(cmd): +def _decompose_rz2rx_P(cmd): """ Decompose the Rz using negative angle. """ qubit = cmd.qubits[0] eng = cmd.engine @@ -35,7 +35,7 @@ def _decompose_rz_P(cmd): Rx(-angle) | qubit Uncompute(eng) -def _decompose_rz_M(cmd): +def _decompose_rz2rx_M(cmd): """ Decompose the Rz using positive angle. """ qubit = cmd.qubits[0] eng = cmd.engine @@ -54,6 +54,6 @@ def _recognize_RzNoCtrl(cmd): #: Decomposition rules all_defined_decomposition_rules = [ - DecompositionRule(Rz, _decompose_rz_P, _recognize_RzNoCtrl), - DecompositionRule(Rz, _decompose_rz_M, _recognize_RzNoCtrl) + DecompositionRule(Rz, _decompose_rz2rx_P, _recognize_RzNoCtrl), + DecompositionRule(Rz, _decompose_rz2rx_M, _recognize_RzNoCtrl) ] diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index f93513fe5..efb18e5bf 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -13,12 +13,17 @@ # limitations under the License. """ -Defines a setup to compile to a restricted gate set. +Apply the restricted gate set setup for trapped ion based quantum computers. -It provides the `engine_list` for the `MainEngine`. This engine list contains -an AutoReplacer with most of the gate decompositions of ProjectQ, which are -used to decompose a circuit into a restricted gate set (with some limitions -on the choice of gates). +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. """ import inspect @@ -31,19 +36,35 @@ TagRemover) from projectq.ops import (Rxx,Rx,Ry) +#List of qubits and the last decomposition used on them +# If the qubit is not on the dictionary, then no decomposition occured +# If the value is -1 then the last gate applied (during a decomposition!) was Ry(-math.pi/2) +# If the value is 1 then the last gate applied (during a decomposition!) was Ry(math.pi/2) +# If the value is 0 then the last gate applied (during a decomposition!) was a Rx liste=dict() def chooser_Ry_reducer(cmd,decomposition_list): - provisory_liste=dict() + """ + choose the decomposition to apply based on the previous decomposition used for the given qubit. + + Note: + Classical instructions gates such as e.g. Flush and Measure are + automatically allowed. + + Returns: + a decomposition object from the decomposition_list + """ + provisory_liste=dict() #Dictionary to evaluate available decomposition as well as the gate that needs to be decomposed name='default' - try: - for el in decomposition_list: + + for el in decomposition_list: + try: decomposition=el.decompose.__name__.split('_') name=decomposition[2] provisory_liste[decomposition[3]]=el - except: - return decomposition_list[0] - if name=='cnot2rxx': + except: + pass + if name=='cnot2rxx': ctrl = cmd.control_qubits idx=str(ctrl) if idx in liste: @@ -56,7 +77,7 @@ def chooser_Ry_reducer(cmd,decomposition_list): else: liste[idx]=-1 return provisory_liste['M'] - elif name=='h': + elif name=='h2rx': qubit = cmd.qubits[0] idx=str(qubit) if idx not in liste: @@ -68,7 +89,7 @@ def chooser_Ry_reducer(cmd,decomposition_list): else: liste[idx]=00 return provisory_liste['N'] - elif name=='rz': + elif name=='rz2rx': qubit = cmd.qubits[0] idx=str(qubit) if idx not in liste: @@ -80,7 +101,7 @@ def chooser_Ry_reducer(cmd,decomposition_list): else: liste[idx]=1 return provisory_liste['P'] - else: + else: #Nothing worked, so decompose the first decompostion function like the default function return decomposition_list[0] @@ -90,8 +111,9 @@ 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 + -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. From 2b76e64248a7efb6118ce53293836855979956e9 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Fri, 6 Dec 2019 14:27:04 +0000 Subject: [PATCH 12/43] Updates to h2rx and cnot2rxx --- docs/projectq.setups.decompositions.rst | 26 +++++++++++++++++++++- projectq/setups/decompositions/cnot2rxx.py | 22 +++++++++--------- projectq/setups/decompositions/h2rx.py | 14 ++++++------ 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/docs/projectq.setups.decompositions.rst b/docs/projectq.setups.decompositions.rst index 26cd392fc..af3916b7b 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,14 @@ projectq.setups.decompositions.globalphase module :members: :undoc-members: -projectq.setups.decompositions.ph2r module +projectq.setups.decompositions.h2rx module +------------------------------------------------- + +.. automodule:: projectq.setups.decompositions.h2rx + :members: + :undoc-members: + +projectq.setups.decompositions.h2rx module ------------------------------------------ .. automodule:: projectq.setups.decompositions.ph2r @@ -132,6 +149,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/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py index 62978f068..113d0250a 100644 --- a/projectq/setups/decompositions/cnot2rxx.py +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -23,30 +23,32 @@ def _decompose_cnot2rxx_M(cmd): """ Decompose CNOT gates. """ + print("decompose_M called") ctrl = cmd.control_qubits - eng = cmd.engine Ry(math.pi/2) | ctrl[0] - Rx(3*math.pi/2)| ctrl[0] - Rx(3*math.pi/2)| cmd.qubits[0][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 gates. """ + print("decompose_P called") ctrl = cmd.control_qubits - eng = cmd.engine Ry(-math.pi/2) | ctrl[0] - Rx(3*math.pi/2)| ctrl[0] - Rx(3*math.pi/2)| cmd.qubits[0][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): - #return True +def _recognize_cnot(cmd): + print('RECOGNIZE CNOT') + print(cmd) + print(get_control_count(cmd)) return get_control_count(cmd) == 1 #: Decomposition rules all_defined_decomposition_rules = [ - DecompositionRule(X.__class__, _decompose_cnot2rxx_P, _recognize_cnot2), - DecompositionRule(X.__class__, _decompose_cnot2rxx_M, _recognize_cnot2) + DecompositionRule(X.__class__, _decompose_cnot2rxx_M, _recognize_cnot), + DecompositionRule(X.__class__, _decompose_cnot2rxx_P, _recognize_cnot) ] diff --git a/projectq/setups/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py index 44867bb04..18c02686c 100644 --- a/projectq/setups/decompositions/h2rx.py +++ b/projectq/setups/decompositions/h2rx.py @@ -23,19 +23,19 @@ from projectq.ops import Rx, Ry, Rz, H -def _decompose_h_N(cmd): +def _decompose_h_P(cmd): """ Decompose the Ry gate.""" + print("decompose_P called") qubit = cmd.qubits[0] - eng = cmd.engine - Ry(-1*math.pi/2) | qubit Rx(math.pi) | qubit + Ry(-1*math.pi/2) | qubit def _decompose_h_M(cmd): """ Decompose the Ry gate.""" + print("decompose_M called") qubit = cmd.qubits[0] - eng = cmd.engine - Rx(-1*math.pi) | qubit Ry(math.pi/2) | qubit + Rx(-1*math.pi) | qubit def _recognize_HNoCtrl(cmd): """ For efficiency reasons only if no control qubits.""" @@ -44,6 +44,6 @@ def _recognize_HNoCtrl(cmd): #: Decomposition rules all_defined_decomposition_rules = [ - DecompositionRule(H.__class__, _decompose_h_N, _recognize_HNoCtrl), - DecompositionRule(H.__class__, _decompose_h_M, _recognize_HNoCtrl) + DecompositionRule(H.__class__, _decompose_h_M, _recognize_HNoCtrl), + DecompositionRule(H.__class__, _decompose_h_P, _recognize_HNoCtrl) ] From 60427da280286ef05c1fd9e3e98411c697e72ab2 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Fri, 6 Dec 2019 15:02:03 +0000 Subject: [PATCH 13/43] Create cnot2rxx_test.py Testing file for cnot2rxx decomposition. Includes updated method for test_decomposition which tests that decomposition produces identical wave-state up to a global phase. --- projectq/setups/decompositions/cnot2rxx.py | 4 +- .../setups/decompositions/cnot2rxx_test.py | 139 ++++++++++++++++++ projectq/setups/decompositions/h2rx_test.py | 2 +- 3 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 projectq/setups/decompositions/cnot2rxx_test.py diff --git a/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py index 35e338e53..7d4e4a349 100644 --- a/projectq/setups/decompositions/cnot2rxx.py +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -44,6 +44,6 @@ def _recognize_cnot2(cmd): #: Decomposition rules all_defined_decomposition_rules = [ - DecompositionRule(X.__class__, _decompose_cnot2rxx_M, _recognize_cnot), - DecompositionRule(X.__class__, _decompose_cnot2rxx_P, _recognize_cnot) + 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..a73d71f7e --- /dev/null +++ b/projectq/setups/decompositions/cnot2rxx_test.py @@ -0,0 +1,139 @@ +# 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 + +import projectq +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 projectq.setups.decompositions 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 """ + g = cmd.gate + 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 + + Note that this function tests the first DecompositionRule in + cnot2rxx.all_defined_decomposition_rules + """ + 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(modules=[cnot2rxx]) + 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) == 9 + + # Create empty vectors for the wave vectors for the correct + # and test qubits + correct_vector = np.zeros((4,1),dtype=np.complex_) + test_vector = np.zeros((4,1),dtype=np.complex_) + + i=0 + for fstate in range(4): + binary_state = format(fstate, '02b') + test = test_sim.get_amplitude(binary_state, + test_qb + test_ctrl_qb) + correct = correct_sim.get_amplitude(binary_state, correct_qb + + correct_ctrl_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) + + 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) + + \ No newline at end of file diff --git a/projectq/setups/decompositions/h2rx_test.py b/projectq/setups/decompositions/h2rx_test.py index 60d67cd98..0015201da 100644 --- a/projectq/setups/decompositions/h2rx_test.py +++ b/projectq/setups/decompositions/h2rx_test.py @@ -45,7 +45,7 @@ def test_recognize_correct_gates(): def h_decomp_gates(eng, cmd): - """ Test that cmd.gate is the gate H """ + """ 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 From 1880aa87e0992e48815d7307a2bc036d447fc6e8 Mon Sep 17 00:00:00 2001 From: logiciqt Date: Tue, 10 Dec 2019 18:08:48 +0000 Subject: [PATCH 14/43] basic rotation gates at an angle of 0 or 2pi are removed by the optimizer. basic rotation gates ignore now the global phase and are defined over 0:2pi --- projectq/cengines/_optimize.py | 17 +++++++++++++++++ projectq/cengines/_optimize_test.py | 18 ++++++++++++++++++ projectq/ops/__init__.py | 1 + projectq/ops/_basics.py | 24 ++++++++++++++++-------- projectq/ops/_basics_test.py | 18 +++++++++++++----- projectq/ops/_command.py | 8 ++++++++ projectq/ops/_command_test.py | 20 ++++++++++++++++---- projectq/ops/_metagates.py | 19 +++++++++++++++++++ projectq/ops/_metagates_test.py | 10 ++++++++++ 9 files changed, 118 insertions(+), 17 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 2e72540b9..24ab986e7 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -130,6 +130,23 @@ 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 + try: + 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 + except: + pass + # 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..ce8f937e5 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -127,3 +127,21 @@ 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(2*math.pi) | qb0 + Ry(2*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/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..eec6c562c 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -40,6 +40,8 @@ from ._command import Command, apply_command +import unicodedata + ANGLE_PRECISION = 12 ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION RTOL = 1e-10 @@ -227,6 +229,9 @@ def __str__(self): def __hash__(self): return hash(str(self)) + def is_identity(self): + return False + class MatrixGate(BasicGate): """ @@ -317,19 +322,19 @@ class BasicRotationGate(BasicGate): A rotation gate has a continuous parameter (the angle), labeled 'angle' / self.angle. Its inverse is the same gate with the negated argument. Rotation gates of the same class can be merged by adding the angles. - The continuous parameter is modulo 4 * pi, self.angle is in the interval - [0, 4 * pi). + The continuous parameter is modulo 2 * pi, self.angle is in the interval + [0, 2 * pi). """ def __init__(self, angle): """ Initialize a basic rotation gate. Args: - angle (float): Angle of rotation (saved modulo 4 * pi) + angle (float): Angle of rotation (saved modulo 2 * pi) """ BasicGate.__init__(self) - rounded_angle = round(float(angle) % (4. * math.pi), ANGLE_PRECISION) - if rounded_angle > 4 * math.pi - ANGLE_TOLERANCE: + rounded_angle = round(float(angle) % (2. * math.pi), ANGLE_PRECISION) + if rounded_angle > 2 * math.pi - ANGLE_TOLERANCE: rounded_angle = 0. self.angle = rounded_angle @@ -343,7 +348,7 @@ def __str__(self): [CLASSNAME]([ANGLE]) """ - return str(self.__class__.__name__) + "(" + str(self.angle) + ")" + return str(self.__class__.__name__) + "(" + str(round(self.angle/math.pi,3)) +unicodedata.lookup("GREEK SMALL LETTER PI")+ ")" def tex_str(self): """ @@ -355,7 +360,7 @@ 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): """ @@ -365,7 +370,7 @@ def get_inverse(self): if self.angle == 0: return self.__class__(0) else: - return self.__class__(-self.angle + 4 * math.pi) + return self.__class__(-self.angle + 2 * math.pi) def get_merged(self, other): """ @@ -401,6 +406,9 @@ def __ne__(self, other): def __hash__(self): return hash(str(self)) + def is_identity(self): + return self.angle == 0. or self.angle==2*math.pi + class BasicPhaseGate(BasicGate): """ diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 80cc80183..3f0e3ddbb 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -163,15 +163,15 @@ 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)" + basic_rotation_gate = _basics.BasicRotationGate(0.5*math.pi) + assert str(basic_rotation_gate) == "BasicRotationGate(0.5π)" 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(0.5*math.pi) + assert basic_rotation_gate.tex_str() == "BasicRotationGate$_{0.5\pi}$" basic_rotation_gate = _basics.BasicRotationGate(4 * math.pi - 1e-13) - assert basic_rotation_gate.tex_str() == "BasicRotationGate$_{0.0}$" + assert basic_rotation_gate.tex_str() == "BasicRotationGate$_{0.0\pi}$" @pytest.mark.parametrize("input_angle, inverse_angle", @@ -193,6 +193,14 @@ def test_basic_rotation_gate_get_merged(): merged_gate = basic_rotation_gate1.get_merged(basic_rotation_gate2) assert merged_gate == basic_rotation_gate3 +def test_basic_rotation_gate_is_identity(): + basic_gate = _basics.BasicGate() + basic_rotation_gate1 = _basics.BasicRotationGate(0.) + basic_rotation_gate2 = _basics.BasicRotationGate(1.0*math.pi) + basic_rotation_gate3 = _basics.BasicRotationGate(2.*math.pi) + assert 1 == basic_rotation_gate1.is_identity() + assert 0 == basic_rotation_gate2.is_identity() + assert 1 == basic_rotation_gate3.is_identity() def test_basic_rotation_gate_comparison_and_hash(): basic_rotation_gate1 = _basics.BasicRotationGate(0.5) diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 5186502fa..87deab827 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -149,6 +149,14 @@ def get_inverse(self): 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 diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index ae1407836..36dcc8c4a 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -132,6 +132,18 @@ def test_command_get_merged(main_engine): with pytest.raises(NotMergeable): 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 inverse_cmd2.gate.is_identity()==False + assert cmd2.gate.is_identity()==False + def test_command_order_qubits(main_engine): qubit0 = Qureg([Qubit(main_engine, 0)]) @@ -232,9 +244,9 @@ 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]" + assert str(cmd) == "CRx(0.5π) | ( Qureg[1], Qureg[0] )" + cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) + assert str(cmd2) == "Rx(0.5π) | Qureg[0]" diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index b2e7959fe..72d262de4 100755 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -132,6 +132,25 @@ 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 + """ + try: + return gate.is_identity() + except : + return False class ControlledGate(BasicGate): """ diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py index c42393b06..834c93131 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 non_identity_gate.is_identity()==False + assert _metagates.is_identity(non_identity_gate) ==False + # Choose gate which is an identity gate: + identity_gate=Rx(0.) + assert identity_gate.is_identity()==True + assert _metagates.is_identity(identity_gate) == True + def test_controlled_gate_init(): one_control = _metagates.ControlledGate(Y, 1) From 47482684e9e42bb69003dc4202af397ab5a51819 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Tue, 10 Dec 2019 19:07:08 +0000 Subject: [PATCH 15/43] Update and create trapped_ion_decomposer_test.py --- projectq/setups/decompositions/cnot2rxx.py | 8 ++ projectq/setups/decompositions/h2rx.py | 15 ++- projectq/setups/decompositions/rz2rx.py | 9 ++ projectq/setups/decompositions/rz2rx_test.py | 35 ++++++- projectq/setups/trapped_ion_decomposer.py | 98 ++++++++++++------- .../setups/trapped_ion_decomposer_test.py | 62 ++++++++++++ 6 files changed, 183 insertions(+), 44 deletions(-) create mode 100644 projectq/setups/trapped_ion_decomposer_test.py diff --git a/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py index 7d4e4a349..15c51a825 100644 --- a/projectq/setups/decompositions/cnot2rxx.py +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -11,6 +11,10 @@ # 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. @@ -23,6 +27,8 @@ 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] Rx(-math.pi/2)| ctrl[0] @@ -32,6 +38,8 @@ def _decompose_cnot2rxx_M(cmd): 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] Rx(-math.pi/2)| ctrl[0] diff --git a/projectq/setups/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py index 8acbb26a2..62f372a62 100644 --- a/projectq/setups/decompositions/h2rx.py +++ b/projectq/setups/decompositions/h2rx.py @@ -11,6 +11,11 @@ # 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. @@ -23,16 +28,18 @@ from projectq.ops import Rx, Ry, Rz, H -def _decompose_h2rx_N(cmd): +def _decompose_h2rx_M(cmd): """ Decompose the Ry gate.""" - print("decompose_P called") + # Labelled 'M' for 'minus' because decomposition + # ends with a Ry(-pi/2) qubit = cmd.qubits[0] Rx(math.pi) | qubit Ry(-1*math.pi/2) | qubit -def _decompose_h2rx_M(cmd): +def _decompose_h2rx_N(cmd): """ Decompose the Ry gate.""" - print("decompose_M called") + # 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 Rx(-1*math.pi) | qubit diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index e58949b8e..08f58089f 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -11,6 +11,11 @@ # 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 @@ -25,6 +30,8 @@ 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 @@ -37,6 +44,8 @@ def _decompose_rz2rx_P(cmd): 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 diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py index 750bee2fd..8f9619cbb 100644 --- a/projectq/setups/decompositions/rz2rx_test.py +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -15,7 +15,7 @@ "Tests for projectq.setups.decompositions.rz2rx.py" import math - +import numpy as np import pytest from projectq import MainEngine @@ -50,10 +50,23 @@ def rz_decomp_gates(eng, cmd): 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 the decomposition of Rz produces correct amplitudes + """ Test that this decomposition of Rz produces correct amplitudes Note that this function tests the first DecompositionRule in rz2rx.all_defined_decomposition_rules @@ -78,13 +91,25 @@ def test_decomposition(angle): Rz(angle) | test_qb test_eng.flush() - assert correct_dummy_eng.received_commands[1].gate == Rz(angle) - assert test_dummy_eng.received_commands[1].gate != Rz(angle) + # 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) - assert correct == pytest.approx(test, rel=1e-12, abs=1e-12) + 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/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index efb18e5bf..c8f90b941 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -11,6 +11,10 @@ # 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. @@ -21,7 +25,7 @@ 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, +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. """ @@ -36,72 +40,96 @@ TagRemover) from projectq.ops import (Rxx,Rx,Ry) -#List of qubits and the last decomposition used on them +# ------------------chooser_Ry_reducer-------------------# +# List of qubits and the last decomposition used on them # If the qubit is not on the dictionary, then no decomposition occured # If the value is -1 then the last gate applied (during a decomposition!) was Ry(-math.pi/2) # If the value is 1 then the last gate applied (during a decomposition!) was Ry(math.pi/2) # If the value is 0 then the last gate applied (during a decomposition!) was a Rx -liste=dict() + +prev_Ry_sign=dict() # Keep 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 to apply based on the previous decomposition used for the given qubit. + Choose the decomposition so as to maximise Ry cancellations, + based on the previous decomposition used for the given qubit. Note: - Classical instructions gates such as e.g. Flush and Measure are - automatically allowed. + Classical instructions gates such as e.g. Flush and + Measure are automatically allowed. Returns: - a decomposition object from the decomposition_list + A decomposition object from the decomposition_list. """ - provisory_liste=dict() #Dictionary to evaluate available decomposition as well as the gate that needs to be decomposed + decomp_rule=dict() name='default' for el in decomposition_list: try: decomposition=el.decompose.__name__.split('_') name=decomposition[2] - provisory_liste[decomposition[3]]=el + decomp_rule[decomposition[3]]=el + # '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: pass if name=='cnot2rxx': ctrl = cmd.control_qubits - idx=str(ctrl) - if idx in liste: - if liste[idx]<=0: - liste[idx]=-1 - return provisory_liste['M'] + idx=str(ctrl) # index of the qubit + if idx in prev_Ry_sign: + if prev_Ry_sign[idx]<=0: + # If the previous qubit had Ry(-pi.2) + # choose the decomposition that starts + # with Ry(pi/2) + prev_Ry_sign[idx]=-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'] else: - liste[idx]=1 - return provisory_liste['P'] + # Previous qubit had Ry(pi/2) + # choose decomposition that starts + # with Ry(-pi/2) and ends with R(pi/2) + prev_Ry_sign[idx]=1 + return decomp_rule['P'] else: - liste[idx]=-1 - return provisory_liste['M'] + # In this case where we have no previous Ry sign + # we choose decomp_rule['M'] + prev_Ry_sign[idx]=-1 + return decomp_rule['M'] elif name=='h2rx': + # Block operates similar to 'cnot2rxx' case qubit = cmd.qubits[0] idx=str(qubit) - if idx not in liste: - liste[idx]=+1 - return provisory_liste['M'] - elif liste[idx]==0: - liste[idx]=+1 - return provisory_liste['M'] + if idx not in prev_Ry_sign: + prev_Ry_sign[idx]=+1 + return decomp_rule['M'] + elif prev_Ry_sign[idx]==0: + prev_Ry_sign[idx]=+1 + return decomp_rule['M'] else: - liste[idx]=00 - return provisory_liste['N'] + prev_Ry_sign[idx]=00 + return decomp_rule['N'] elif name=='rz2rx': + # Block operates similar to 'cnot2rxx' case qubit = cmd.qubits[0] idx=str(qubit) - if idx not in liste: - liste[idx]=-1 - return provisory_liste['M'] - elif liste[idx]<=0: - liste[idx]=-1 - return provisory_liste['M'] + if idx not in prev_Ry_sign: + prev_Ry_sign[idx]=-1 + return decomp_rule['M'] + elif prev_Ry_sign[idx]<=0: + prev_Ry_sign[idx]=-1 + return decomp_rule['M'] else: - liste[idx]=1 - return provisory_liste['P'] - else: #Nothing worked, so decompose the first decompostion function like the default function + prev_Ry_sign[idx]=1 + return decomp_rule['P'] + else: # Nothing worked, so use the first decompostion + # in the list like the default function return decomposition_list[0] diff --git a/projectq/setups/trapped_ion_decomposer_test.py b/projectq/setups/trapped_ion_decomposer_test.py new file mode 100644 index 000000000..5a59458b4 --- /dev/null +++ b/projectq/setups/trapped_ion_decomposer_test.py @@ -0,0 +1,62 @@ +# 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 math +import projectq +from projectq import MainEngine +from projectq.ops import Rx, Ry, Rz, H, CNOT, Measure, All, X, Rxx +from projectq.meta import Compute, Control, Uncompute +from projectq.cengines import DecompositionRule +from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, + InstructionFilter) +import pytest +import projectq.setups.restrictedgateset as restrictedgateset +from projectq.setups.trapped_ion_decomposer import 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 and Measure commands, this would +# result in 12 commands. +# Using the chooser_Rx_reducer you get 9 commands, since you +# now have 4 single qubit gates and 1 two qubit gate. + +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 and Measure commands, this + results in 12 commands. + Using the chooser_Rx_reducer you get 9 commands, the + decomposotion having resulted in 4 single qubit gates + and 1 two qubit gate. + """ + engine_list = restrictedgateset.get_engine_list( + one_qubit_gates=(Rx, Ry), + two_qubit_gates=(Rxx,), compiler_chooser=chooser_Ry_reducer) + 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 + assert len(backend.received_commands) < 12 + From 5ac73184bd6c39609752476b7e91a23300017e7d Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Tue, 10 Dec 2019 19:26:48 +0000 Subject: [PATCH 16/43] Minor update Documentation and comments. --- docs/projectq.setups.decompositions.rst | 6 ------ docs/projectq.setups.rst | 7 +++++++ projectq/setups/trapped_ion_decomposer.py | 15 ++++++++------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/projectq.setups.decompositions.rst b/docs/projectq.setups.decompositions.rst index af3916b7b..6206c4a95 100755 --- a/docs/projectq.setups.decompositions.rst +++ b/docs/projectq.setups.decompositions.rst @@ -100,12 +100,6 @@ projectq.setups.decompositions.globalphase module :members: :undoc-members: -projectq.setups.decompositions.h2rx module -------------------------------------------------- - -.. automodule:: projectq.setups.decompositions.h2rx - :members: - :undoc-members: projectq.setups.decompositions.h2rx 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/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index c8f90b941..f7692bff7 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -41,13 +41,12 @@ from projectq.ops import (Rxx,Rx,Ry) # ------------------chooser_Ry_reducer-------------------# -# List of qubits and the last decomposition used on them -# If the qubit is not on the dictionary, then no decomposition occured +# 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) # If the value is 1 then the last gate applied (during a decomposition!) was Ry(math.pi/2) # If the value is 0 then the last gate applied (during a decomposition!) was a Rx -prev_Ry_sign=dict() # Keep track of most recent Ry sign, i.e. +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 @@ -58,7 +57,7 @@ def chooser_Ry_reducer(cmd,decomposition_list): based on the previous decomposition used for the given qubit. Note: - Classical instructions gates such as e.g. Flush and + Classical instructions gates e.g. Flush and Measure are automatically allowed. Returns: @@ -83,7 +82,7 @@ def chooser_Ry_reducer(cmd,decomposition_list): idx=str(ctrl) # index of the qubit if idx in prev_Ry_sign: if prev_Ry_sign[idx]<=0: - # If the previous qubit had Ry(-pi.2) + # If the previous qubit had Ry(-pi/2) # choose the decomposition that starts # with Ry(pi/2) prev_Ry_sign[idx]=-1 @@ -128,7 +127,7 @@ def chooser_Ry_reducer(cmd,decomposition_list): else: prev_Ry_sign[idx]=1 return decomp_rule['P'] - else: # Nothing worked, so use the first decompostion + else: # No decomposition chosen, so use the first decompostion # in the list like the default function return decomposition_list[0] @@ -141,7 +140,9 @@ def get_engine_list(): 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! + -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. From cf6a793f36b8f193122e07a03bdfdcc0f31656d6 Mon Sep 17 00:00:00 2001 From: logiciqt Date: Tue, 10 Dec 2019 19:28:23 +0000 Subject: [PATCH 17/43] Update on comments regarding Identity gates --- projectq/cengines/_optimize.py | 2 +- projectq/ops/_basics.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 24ab986e7..758aeaccb 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. diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index eec6c562c..018ec14b1 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -406,7 +406,10 @@ def __ne__(self, other): def __hash__(self): return hash(str(self)) - def is_identity(self): + def is_identity(self): + """ + Return True if the gate is equivalent to an Identity gate + """ return self.angle == 0. or self.angle==2*math.pi From acdf41a2799b1f919083ee94466452fcdee7e47f Mon Sep 17 00:00:00 2001 From: logiciqt Date: Thu, 19 Dec 2019 17:25:16 +0000 Subject: [PATCH 18/43] Changes 1/2 : command can be printed with unicode symbols only via new to_String method; syntax correction; --- projectq/ops/_basics.py | 14 ++++++++++++-- projectq/ops/_basics_test.py | 10 ++++++---- projectq/ops/_command.py | 10 +++++++--- projectq/ops/_command_test.py | 18 ++++++++++++++---- projectq/ops/_metagates.py | 5 +---- projectq/ops/_metagates_test.py | 8 ++++---- projectq/setups/trapped_ion_decomposer.py | 14 ++++++-------- 7 files changed, 50 insertions(+), 29 deletions(-) diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 018ec14b1..bd777aecd 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -226,6 +226,9 @@ def __ne__(self, other): def __str__(self): raise NotImplementedError('This gate does not implement __str__.') + def to_String(self,symbols): + return str(self) + def __hash__(self): return hash(str(self)) @@ -338,7 +341,10 @@ def __init__(self, angle): rounded_angle = 0. self.angle = rounded_angle - def __str__(self): + def __str__(self,symbols=False): + return self.to_String() + + def to_String(self,symbols=False): """ Return the string representation of a BasicRotationGate. @@ -348,7 +354,11 @@ def __str__(self): [CLASSNAME]([ANGLE]) """ - return str(self.__class__.__name__) + "(" + str(round(self.angle/math.pi,3)) +unicodedata.lookup("GREEK SMALL LETTER PI")+ ")" + 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): """ diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 3f0e3ddbb..61a229fd2 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -163,8 +163,10 @@ def test_basic_rotation_gate_init(input_angle, modulo_angle): def test_basic_rotation_gate_str(): + basic_rotation_gate = _basics.BasicRotationGate(math.pi) + assert str(basic_rotation_gate) == "BasicRotationGate(3.14159265359)" basic_rotation_gate = _basics.BasicRotationGate(0.5*math.pi) - assert str(basic_rotation_gate) == "BasicRotationGate(0.5π)" + assert basic_rotation_gate.to_String(symbols=True) == "BasicRotationGate(0.5π)" def test_basic_rotation_tex_str(): @@ -198,9 +200,9 @@ def test_basic_rotation_gate_is_identity(): basic_rotation_gate1 = _basics.BasicRotationGate(0.) basic_rotation_gate2 = _basics.BasicRotationGate(1.0*math.pi) basic_rotation_gate3 = _basics.BasicRotationGate(2.*math.pi) - assert 1 == basic_rotation_gate1.is_identity() - assert 0 == basic_rotation_gate2.is_identity() - assert 1 == basic_rotation_gate3.is_identity() + assert basic_rotation_gate1.is_identity() + assert not basic_rotation_gate2.is_identity() + assert basic_rotation_gate3.is_identity() def test_basic_rotation_gate_comparison_and_hash(): basic_rotation_gate1 = _basics.BasicRotationGate(0.5) diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 87deab827..1ea85b6f4 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -153,7 +153,8 @@ 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 + Returns: + True if the gate is equivalent to an Identity gate, False otherwise """ return projectq.ops.is_identity(self.gate) @@ -304,7 +305,10 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) - def __str__(self): + def __str__(self,symbols=False): + return self.to_String() + + def to_String(self,symbols=False): """ Get string representation of this Command object. """ @@ -322,4 +326,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 36dcc8c4a..8db4b75a8 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -141,8 +141,8 @@ def test_command_is_identity(main_engine): inverse_cmd2 = cmd2.get_inverse() assert inverse_cmd.gate.is_identity() assert cmd.gate.is_identity() - assert inverse_cmd2.gate.is_identity()==False - assert cmd2.gate.is_identity()==False + assert not inverse_cmd2.gate.is_identity() + assert not cmd2.gate.is_identity() def test_command_order_qubits(main_engine): @@ -247,6 +247,16 @@ def test_command_str(): 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] )" + assert str(cmd) == "CRx(1.570796326795‬) | ( Qureg[1], Qureg[0] )" cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) - assert str(cmd2) == "Rx(0.5π) | Qureg[0]" + assert str(cmd2) == "Rx(1.570796326795‬) | 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) + assert cmd.to_String(symbols=True) == "CRx(0.5π) | ( Qureg[1], Qureg[0] )" + cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) + assert cmd2.to_String(symbols=True) == "Rx(0.5π) | Qureg[0]" \ No newline at end of file diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index 72d262de4..cca5e7412 100755 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -147,10 +147,7 @@ def is_identity(gate): get_inverse(Rx(2*math.pi)) # returns True get_inverse(Rx(math.pi)) # returns False """ - try: - return gate.is_identity() - except : - return False + return gate.is_identity() class ControlledGate(BasicGate): """ diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py index 834c93131..8632a99e5 100755 --- a/projectq/ops/_metagates_test.py +++ b/projectq/ops/_metagates_test.py @@ -125,12 +125,12 @@ def test_get_inverse(): def test_is_identity(): # Choose gate which is not an identity gate: non_identity_gate=Rx(0.5) - assert non_identity_gate.is_identity()==False - assert _metagates.is_identity(non_identity_gate) ==False + 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()==True - assert _metagates.is_identity(identity_gate) == True + assert identity_gate.is_identity() + assert _metagates.is_identity(identity_gate) def test_controlled_gate_init(): diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index f7692bff7..dc9c0c893 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -75,11 +75,10 @@ def chooser_Ry_reducer(cmd,decomposition_list): # 'N' stands for neutral # e.g. decomp_rule['M'] will give you the # decomposition_rule that ends with a Ry(-pi/2) - except: + except IndexError: pass if name=='cnot2rxx': - ctrl = cmd.control_qubits - idx=str(ctrl) # index of the qubit + idx = [qb.id for qb in cmd.control_qubits] # index of the qubit if idx in prev_Ry_sign: if prev_Ry_sign[idx]<=0: # If the previous qubit had Ry(-pi/2) @@ -103,8 +102,7 @@ def chooser_Ry_reducer(cmd,decomposition_list): return decomp_rule['M'] elif name=='h2rx': # Block operates similar to 'cnot2rxx' case - qubit = cmd.qubits[0] - idx=str(qubit) + idx = [qb.id for qb in cmd.qubits[0]] if idx not in prev_Ry_sign: prev_Ry_sign[idx]=+1 return decomp_rule['M'] @@ -116,8 +114,7 @@ def chooser_Ry_reducer(cmd,decomposition_list): return decomp_rule['N'] elif name=='rz2rx': # Block operates similar to 'cnot2rxx' case - qubit = cmd.qubits[0] - idx=str(qubit) + idx = [qb.id for qb in cmd.qubits[0]] if idx not in prev_Ry_sign: prev_Ry_sign[idx]=-1 return decomp_rule['M'] @@ -147,7 +144,8 @@ def get_engine_list(): 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) + return restrictedgateset.get_engine_list(one_qubit_gates=(Rx,Ry), + two_qubit_gates=(Rxx,),compiler_chooser=chooser_Ry_reducer) From 27f820cac337c81dbc037b5e3e0381b7e31db5b3 Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Fri, 20 Dec 2019 12:26:05 +0000 Subject: [PATCH 19/43] Work in progress, is commutable --- projectq/cengines/_optimize.py | 20 +++++++++++++++++++- projectq/cengines/_optimize_test.py | 23 ++++++++++++++++++++++- projectq/ops/_basics.py | 3 ++- projectq/ops/_command.py | 16 ++++++++++++++++ projectq/ops/_metagates.py | 2 +- 5 files changed, 60 insertions(+), 4 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 24ab986e7..ad18b935b 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -125,7 +125,10 @@ def _optimize(self, idx, lim=None): i = 0 new_gateloc = 0 limit = len(self._l[idx]) - if lim is not None: + if lim is not None: # the argument for the limit of number + # of operations is not None, then set the + # limit to this number, otherwise the limit + # is the number of commands on this qubit limit = lim new_gateloc = limit @@ -136,6 +139,7 @@ def _optimize(self, idx, lim=None): # determine index of this gate on all qubits qubitids = [qb.id for sublist in self._l[idx][i].all_qubits for qb in sublist] + print([str(qubitid) for qubitid in qubitids]) gid = self._get_gate_indices(idx, i, qubitids) for j in range(len(qubitids)): new_list = (self._l[qubitids[j]][0:gid[j]] + @@ -147,6 +151,7 @@ def _optimize(self, idx, lim=None): except: pass + # can be dropped if two in a row are self-inverses inv = self._l[idx][i].get_inverse() @@ -171,8 +176,20 @@ def _optimize(self, idx, lim=None): limit -= 2 continue + # Gates are not mergeable, see if they are commutable + #try: + command1 = self._l[idx][i] + command2 = self._l[idx][i + 1] + print("Command1") + print(command1) + print("Command2") + print(command2) + commutable = command1.is_commutable(command2) # gates are not each other's inverses --> check if they're # mergeable + # except: + # pass # gates not commutable. + try: merged_command = self._l[idx][i].get_merged( self._l[idx][i + 1]) @@ -200,6 +217,7 @@ def _optimize(self, idx, lim=None): pass # can't merge these two commands. i += 1 # next iteration: look at next gate + return limit def _check_and_send(self): diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index ce8f937e5..ded37810a 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -15,10 +15,11 @@ """Tests for projectq.cengines._optimize.py.""" import pytest +import math from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import (CNOT, H, Rx, Ry, AllocateQubitGate, X, +from projectq.ops import (CNOT, H, Rx, Ry, Rxx, AllocateQubitGate, X, FastForwardingGate, ClassicalInstructionGate) from projectq.cengines import _optimize @@ -145,3 +146,23 @@ def test_local_optimizer_identity_gates(): # Expect allocate, one Rx gate, and flush gate assert len(backend.received_commands) == 3 assert backend.received_commands[1].gate == Rx(0.5) + +def test_local_optimizer_commutable_gates(): + #local_optimizer = _optimize.LocalOptimizer(m=4) + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend) + # # Test that it commutes commutable gates such as Rx, Rxx + qb0 = eng.allocate_qubit() + qb1 = eng.allocate_qubit() + Rx(math.pi/2) | qb0 + Rxx(math.pi/2) | (qb0, qb1) + Rx(math.pi/2) | qb0 + eng.flush() + command1 = backend.received_commands[0] + command2 = backend.received_commands[1] + commutable = command1.is_commutable(command2) + print("commutable") + print(commutable) + #print("Commands") + #print([str(cmd) for cmd in backend.received_commands]) + return 0 \ No newline at end of file diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index eec6c562c..45aba5fa3 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -232,6 +232,8 @@ def __hash__(self): def is_identity(self): return False + def is_commutable(self, other): + return False class MatrixGate(BasicGate): """ @@ -409,7 +411,6 @@ def __hash__(self): def is_identity(self): return self.angle == 0. or self.angle==2*math.pi - class BasicPhaseGate(BasicGate): """ Defines a base class of a phase gate. diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 87deab827..57110c019 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -156,6 +156,22 @@ def is_identity(self): Returns: True if the gate is equivalent to an Identity gate, False otherwise """ return projectq.ops.is_identity(self.gate) + + def is_commutable(self, other): + """ + Evaluate if the gate called in the command object is commutable with the next + gate. + + Returns: True if the gates are commutable, False otherwise + """ + if (self.qubits != other.qubits): + return False + print("self.gate") + print(self.gate) + print("other.gate") + print(other.gate) + return 0 #self.gate.is_commutable(self.gate, other.gate) + def get_merged(self, other): """ diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index 72d262de4..8d03330dc 100755 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -149,7 +149,7 @@ def is_identity(gate): """ try: return gate.is_identity() - except : + except: return False class ControlledGate(BasicGate): From 419276cbd15089e1424a609bc4de0b0193de28cc Mon Sep 17 00:00:00 2001 From: Daisy Smith Date: Fri, 20 Dec 2019 17:10:08 +0000 Subject: [PATCH 20/43] Update to decomposition test files rz2rx_test now tests each decomposition defined in rz2rx.all_defined_decomposition_rules Similar for cnot2rxx_test and h2rx_test --- .../setups/decompositions/cnot2rxx_test.py | 112 +++++++++--------- projectq/setups/decompositions/h2rx_test.py | 94 ++++++++------- projectq/setups/decompositions/rz2rx_test.py | 88 +++++++------- 3 files changed, 149 insertions(+), 145 deletions(-) diff --git a/projectq/setups/decompositions/cnot2rxx_test.py b/projectq/setups/decompositions/cnot2rxx_test.py index a73d71f7e..f37f7d587 100644 --- a/projectq/setups/decompositions/cnot2rxx_test.py +++ b/projectq/setups/decompositions/cnot2rxx_test.py @@ -73,67 +73,67 @@ def _decomp_gates(eng, cmd): def test_decomposition(): """ Test that this decomposition of CNOT produces correct amplitudes - Note that this function tests the first DecompositionRule in + Function tests each DecompositionRule in cnot2rxx.all_defined_decomposition_rules """ - 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(modules=[cnot2rxx]) - 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() + 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) + 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() + test_eng.flush() + correct_eng.flush() - assert len(correct_dummy_eng.received_commands) == 5 - assert len(test_dummy_eng.received_commands) == 9 + assert len(correct_dummy_eng.received_commands) == 5 + assert len(test_dummy_eng.received_commands) == 9 - # Create empty vectors for the wave vectors for the correct - # and test qubits - correct_vector = np.zeros((4,1),dtype=np.complex_) - test_vector = np.zeros((4,1),dtype=np.complex_) - - i=0 - for fstate in range(4): - binary_state = format(fstate, '02b') - test = test_sim.get_amplitude(binary_state, - test_qb + test_ctrl_qb) - correct = correct_sim.get_amplitude(binary_state, correct_qb + - correct_ctrl_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) - - 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) + # Create empty vectors for the wave vectors for the correct + # and test qubits + correct_vector = np.zeros((4,1),dtype=np.complex_) + test_vector = np.zeros((4,1),dtype=np.complex_) + i=0 + for fstate in range(4): + binary_state = format(fstate, '02b') + test = test_sim.get_amplitude(binary_state, + test_qb + test_ctrl_qb) + correct = correct_sim.get_amplitude(binary_state, correct_qb + + correct_ctrl_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) + + 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) \ No newline at end of file diff --git a/projectq/setups/decompositions/h2rx_test.py b/projectq/setups/decompositions/h2rx_test.py index 0015201da..caa266c0b 100644 --- a/projectq/setups/decompositions/h2rx_test.py +++ b/projectq/setups/decompositions/h2rx_test.py @@ -69,51 +69,53 @@ def h_decomp_gates(eng, cmd): def test_decomposition(): """ Test that this decomposition of H produces correct amplitudes - Note that this function tests the first DecompositionRule in + Function tests each DecompositionRule in h2rx.all_defined_decomposition_rules """ - 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(modules=[h2rx]) - 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() - H | correct_qb - correct_eng.flush() - - test_qb = test_eng.allocate_qubit() - H | test_qb - test_eng.flush() - - assert correct_dummy_eng.received_commands[1].gate == H - assert test_dummy_eng.received_commands[1].gate != H - - # 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 \ No newline at end of file + decomposition_rule_list = h2rx.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(h_decomp_gates), + test_dummy_eng]) + + correct_qb = correct_eng.allocate_qubit() + H | correct_qb + correct_eng.flush() + + test_qb = test_eng.allocate_qubit() + H | test_qb + test_eng.flush() + + assert correct_dummy_eng.received_commands[1].gate == H + assert test_dummy_eng.received_commands[1].gate != H + + # 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 \ No newline at end of file diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py index 8f9619cbb..ca4ef68da 100644 --- a/projectq/setups/decompositions/rz2rx_test.py +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -68,48 +68,50 @@ def rz_decomp_gates(eng, cmd): def test_decomposition(angle): """ Test that this decomposition of Rz produces correct amplitudes - Note that this function tests the first DecompositionRule in + Note that this function tests each DecompositionRule in rz2rx.all_defined_decomposition_rules """ - 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(modules=[rz2rx]) - 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 + 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 From ff74c8bf852347c71e233d8cdd6da072107477a3 Mon Sep 17 00:00:00 2001 From: logiciqt Date: Fri, 20 Dec 2019 21:13:45 +0000 Subject: [PATCH 21/43] Revert "Work in progress, is commutable" This reverts commit 27f820cac337c81dbc037b5e3e0381b7e31db5b3. --- projectq/cengines/_optimize.py | 20 +------------------- projectq/cengines/_optimize_test.py | 23 +---------------------- projectq/ops/_basics.py | 3 +-- projectq/ops/_command.py | 16 ---------------- 4 files changed, 3 insertions(+), 59 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index e7cfe22f8..758aeaccb 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -125,10 +125,7 @@ def _optimize(self, idx, lim=None): i = 0 new_gateloc = 0 limit = len(self._l[idx]) - if lim is not None: # the argument for the limit of number - # of operations is not None, then set the - # limit to this number, otherwise the limit - # is the number of commands on this qubit + if lim is not None: limit = lim new_gateloc = limit @@ -139,7 +136,6 @@ def _optimize(self, idx, lim=None): # determine index of this gate on all qubits qubitids = [qb.id for sublist in self._l[idx][i].all_qubits for qb in sublist] - print([str(qubitid) for qubitid in qubitids]) gid = self._get_gate_indices(idx, i, qubitids) for j in range(len(qubitids)): new_list = (self._l[qubitids[j]][0:gid[j]] + @@ -151,7 +147,6 @@ def _optimize(self, idx, lim=None): except: pass - # can be dropped if two in a row are self-inverses inv = self._l[idx][i].get_inverse() @@ -176,20 +171,8 @@ def _optimize(self, idx, lim=None): limit -= 2 continue - # Gates are not mergeable, see if they are commutable - #try: - command1 = self._l[idx][i] - command2 = self._l[idx][i + 1] - print("Command1") - print(command1) - print("Command2") - print(command2) - commutable = command1.is_commutable(command2) # gates are not each other's inverses --> check if they're # mergeable - # except: - # pass # gates not commutable. - try: merged_command = self._l[idx][i].get_merged( self._l[idx][i + 1]) @@ -217,7 +200,6 @@ def _optimize(self, idx, lim=None): pass # can't merge these two commands. i += 1 # next iteration: look at next gate - return limit def _check_and_send(self): diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index ded37810a..ce8f937e5 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -15,11 +15,10 @@ """Tests for projectq.cengines._optimize.py.""" import pytest -import math from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import (CNOT, H, Rx, Ry, Rxx, AllocateQubitGate, X, +from projectq.ops import (CNOT, H, Rx, Ry, AllocateQubitGate, X, FastForwardingGate, ClassicalInstructionGate) from projectq.cengines import _optimize @@ -146,23 +145,3 @@ def test_local_optimizer_identity_gates(): # Expect allocate, one Rx gate, and flush gate assert len(backend.received_commands) == 3 assert backend.received_commands[1].gate == Rx(0.5) - -def test_local_optimizer_commutable_gates(): - #local_optimizer = _optimize.LocalOptimizer(m=4) - backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend) - # # Test that it commutes commutable gates such as Rx, Rxx - qb0 = eng.allocate_qubit() - qb1 = eng.allocate_qubit() - Rx(math.pi/2) | qb0 - Rxx(math.pi/2) | (qb0, qb1) - Rx(math.pi/2) | qb0 - eng.flush() - command1 = backend.received_commands[0] - command2 = backend.received_commands[1] - commutable = command1.is_commutable(command2) - print("commutable") - print(commutable) - #print("Commands") - #print([str(cmd) for cmd in backend.received_commands]) - return 0 \ No newline at end of file diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 3d154adbe..bd777aecd 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -235,8 +235,6 @@ def __hash__(self): def is_identity(self): return False - def is_commutable(self, other): - return False class MatrixGate(BasicGate): """ @@ -424,6 +422,7 @@ def is_identity(self): """ return self.angle == 0. or self.angle==2*math.pi + class BasicPhaseGate(BasicGate): """ Defines a base class of a phase gate. diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index b51a08be6..1ea85b6f4 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -157,22 +157,6 @@ def is_identity(self): True if the gate is equivalent to an Identity gate, False otherwise """ return projectq.ops.is_identity(self.gate) - - def is_commutable(self, other): - """ - Evaluate if the gate called in the command object is commutable with the next - gate. - - Returns: True if the gates are commutable, False otherwise - """ - if (self.qubits != other.qubits): - return False - print("self.gate") - print(self.gate) - print("other.gate") - print(other.gate) - return 0 #self.gate.is_commutable(self.gate, other.gate) - def get_merged(self, other): """ From df7466014d997da82482621f26b0cd8f578ebb5a Mon Sep 17 00:00:00 2001 From: dbretaud Date: Thu, 2 Jan 2020 14:19:12 +0000 Subject: [PATCH 22/43] ibmq fix: projectq uses new ibmq API (no doc available, just look at qiskit). Connection fixed for 5qb devices, the 15qb melbourne device and the online simulator coupling map obtained from ibm server instead of being manually written one setup can be used for the 3 different backend --- projectq/backends/_ibm/_ibm.py | 80 +++-- projectq/backends/_ibm/_ibm_http_client.py | 295 +++++++++++------- .../backends/_ibm/_ibm_http_client_test.py | 159 +++++----- projectq/backends/_ibm/_ibm_test.py | 7 +- projectq/cengines/_basicmapper.py | 4 + projectq/cengines/_ibm5qubitmapper.py | 13 +- projectq/setups/ibm.py | 93 ++++-- projectq/setups/ibm_test.py | 14 +- 8 files changed, 390 insertions(+), 275 deletions(-) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index b1899f043..20dc2a645 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -13,7 +13,7 @@ # limitations under the License. """ Back-end to run quantum program on IBM's Quantum Experience.""" - +import math import random import json @@ -36,16 +36,16 @@ Barrier, FlushGate) -from ._ibm_http_client import send, retrieve +from ._ibm_http_client_v2 import send, retrieve -class IBMBackend(BasicEngine): +class IBMBackend_v2(BasicEngine): """ - The IBM Backend class, which stores the circuit, transforms it to JSON - QASM, and sends the circuit through the IBM API. + The IBM Backend class, which stores the circuit, transforms it to JSON, + and sends the circuit through the IBM API. """ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, - user=None, password=None, device='ibmqx4', + token='', device='ibmq_essex', num_retries=3000, interval=1, retrieve_execution=None): """ @@ -59,10 +59,8 @@ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, verbose (bool): If True, statistics are printed, in addition to the measurement result being registered (at the end of the circuit). - user (string): IBM Quantum Experience user name - password (string): IBM Quantum Experience password - device (string): Device to use ('ibmqx4', or 'ibmqx5') - if use_hardware is set to True. Default is ibmqx4. + token (str): IBM quantum experience user password. + device (str): name of the IBM device to use. ibmq_essex By default num_retries (int): Number of times to retry to obtain results from the IBM API. (default is 3000) interval (float, int): Number of seconds between successive @@ -76,15 +74,15 @@ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, if use_hardware: self.device = device else: - self.device = 'simulator' + self.device = 'ibmq_qasm_simulator' self._num_runs = num_runs self._verbose = verbose - self._user = user - self._password = password + self._token=token self._num_retries = num_retries self._interval = interval self._probabilities = dict() self.qasm = "" + self._json=[] self._measured_ids = [] self._allocated_qubits = set() self._retrieve_execution = retrieve_execution @@ -93,8 +91,8 @@ def is_available(self, cmd): """ Return true if the command can be executed. - The IBM quantum chip can do X, Y, Z, T, Tdag, S, Sdag, - rotation gates, barriers, and CX / CNOT. + The IBM quantum chip can only do U1,U2,U3,barriers, and CX / CNOT. + Conversion implemented for Rotation gates and H gates. Args: cmd (Command): Command for which to check availability @@ -103,7 +101,7 @@ def is_available(self, cmd): if g == NOT and get_control_count(cmd) <= 1: return True if get_control_count(cmd) == 0: - if g in (T, Tdag, S, Sdag, H, Y, Z): + if g == H: return True if isinstance(g, (Rx, Ry, Rz)): return True @@ -111,6 +109,11 @@ def is_available(self, cmd): return True return False + def get_qasm(self): + """ Return the QASM representation of the circuit sent to the backend. + Should be called AFTER calling the ibm device """ + return self.qasm + def _reset(self): """ Reset all temporary variables (after flush gate). """ self._clear = True @@ -129,6 +132,7 @@ def _store(self, cmd): self._probabilities = dict() self._clear = False self.qasm = "" + self._json=[] self._allocated_qubits = set() gate = cmd.gate @@ -154,6 +158,7 @@ def _store(self, cmd): ctrl_pos = cmd.control_qubits[0].id qb_pos = cmd.qubits[0][0].id self.qasm += "\ncx q[{}], q[{}];".format(ctrl_pos, qb_pos) + self._json.append({'qubits': [ctrl_pos, qb_pos], 'name': 'cx'}) elif gate == Barrier: qb_pos = [qb.id for qr in cmd.qubits for qb in qr] self.qasm += "\nbarrier " @@ -161,13 +166,26 @@ def _store(self, cmd): for pos in qb_pos: qb_str += "q[{}], ".format(pos) self.qasm += qb_str[:-2] + ";" + self._json.append({'qubits': [qb_pos], 'name': 'barrier'}) elif isinstance(gate, (Rx, Ry, Rz)): assert get_control_count(cmd) == 0 qb_pos = cmd.qubits[0][0].id u_strs = {'Rx': 'u3({}, -pi/2, pi/2)', 'Ry': 'u3({}, 0, 0)', 'Rz': 'u1({})'} - gate = u_strs[str(gate)[0:2]].format(gate.angle) - self.qasm += "\n{} q[{}];".format(gate, qb_pos) + u_name = {'Rx': 'u3', 'Ry': 'u3', + 'Rz': 'u1'} + u_angle = {'Rx': [gate.angle, -math.pi/2, math.pi/2], 'Ry': [gate.angle, 0, 0], + 'Rz': [gate.angle]} + gate_qasm = u_strs[str(gate)[0:2]].format(gate.angle) + gate_name=u_name[str(gate)[0:2]] + params= u_angle[str(gate)[0:2]] + self.qasm += "\n{} q[{}];".format(gate_qasm, qb_pos) + self._json.append({'qubits': [qb_pos], 'name': gate_name,'params': params}) + elif gate == H: + assert get_control_count(cmd) == 0 + qb_pos = cmd.qubits[0][0].id + self.qasm += "\nu2(0,pi/2) q[{}];".format(qb_pos) + self._json.append({'qubits': [qb_pos], 'name': 'u2','params': [0, 3.141592653589793]}) else: assert get_control_count(cmd) == 0 if str(gate) in self._gate_names: @@ -177,6 +195,7 @@ def _store(self, cmd): qb_pos = cmd.qubits[0][0].id self.qasm += "\n{} q[{}];".format(gate_str, qb_pos) + self._json.append({'qubits': [qb_pos], 'name': gate_str}) def _logical_to_physical(self, qb_id): """ @@ -237,15 +256,15 @@ def _run(self): """ Run the circuit. - Send the circuit via the IBM API (JSON QASM) using the provided user - data / ask for username & password. + Send the circuit via a non documented IBM API (using JSON written circuits) using the provided user + data / ask for the user token. """ # finally: add measurements (no intermediate measurements are allowed) for measured_id in self._measured_ids: qb_loc = self.main_engine.mapper.current_mapping[measured_id] self.qasm += "\nmeasure q[{}] -> c[{}];".format(qb_loc, qb_loc) - + self._json.append({'qubits': [qb_loc], 'name': 'measure','memory':[qb_loc]}) # return if no operations / measurements have been performed. if self.qasm == "": return @@ -255,22 +274,23 @@ def _run(self): self.qasm).format(nq=max_qubit_id + 1) info = {} info['qasms'] = [{'qasm': qasm}] + info['json']=self._json + info['nq']=max_qubit_id + 1 + info['shots'] = self._num_runs - info['maxCredits'] = 5 + info['maxCredits'] = 10 info['backend'] = {'name': self.device} - info = json.dumps(info) - try: if self._retrieve_execution is None: res = send(info, device=self.device, - user=self._user, password=self._password, + token=self._token, shots=self._num_runs, num_retries=self._num_retries, interval=self._interval, verbose=self._verbose) else: - res = retrieve(device=self.device, user=self._user, - password=self._password, + res = retrieve(device=self.device, + token=self._token, jobid=self._retrieve_execution, num_retries=self._num_retries, interval=self._interval, @@ -281,10 +301,12 @@ def _run(self): P = random.random() p_sum = 0. measured = "" + length=len(self._measured_ids) for state in counts: probability = counts[state] * 1. / self._num_runs - state = list(reversed(state)) - state = "".join(state) + state=state.split('x')[1] + state="{0:b}".format(int(state)) + state=state.zfill(length) p_sum += probability star = "" if p_sum >= P and measured == "": diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index d713b17fa..8de1ab6bc 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -21,75 +21,220 @@ import sys import time from requests.compat import urljoin +from requests import Session + +_auth_api_url = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' +_api_url = 'https://api.quantum-computing.ibm.com/api/' +CLIENT_APPLICATION = 'ibmqprovider/0.4.4'#TODO: call to get the API version automatically + + +class IBMQ(Session): + def __init__(self): + super().__init__() + self.backends=dict() + self.timeout=5.0 + + def get_list_devices(self,verbose=False): + list_device_url='Network/ibm-q/Groups/open/Projects/main/devices/v/1' + argument={'allow_redirects': True, 'timeout': (self.timeout, None)} + r = super().request('GET', urljoin(_api_url, list_device_url), **argument) + + r.raise_for_status() + r_json=r.json() + self.backends=dict() + for el in r_json: + self.backends[el['backend_name']]={'nq':el['n_qubits'],'coupling_map':el['coupling_map'],'version':el['backend_version']} + if verbose==True: + print('- List of IBMQ devices available:') + print(self.backends) + return self.backends + + def is_online(self,device): + return device in self.backends + + def can_run_experiment(self,info,device): + """ + check if the device is big enough to run the code + + Args: + info (dict): dictionary sent by the backend containing the code to run + device (str): name of the ibm device to use + :return: + (bool): True if device is big enough, False otherwise + """ + nb_qubit_max=self.backends[device]['nq'] + nb_qubit_needed=info['nq'] + return nb_qubit_needed<=nb_qubit_max,nb_qubit_max,nb_qubit_needed + + def _authenticate(self,token=None): + """ + :param token: + :return: + """ + if token is None: + token = getpass.getpass(prompt='IBM Q token > ') + self.headers.update({'X-Qx-Client-Application': CLIENT_APPLICATION}) + args={'data': None, 'json': {'apiToken': token}, 'timeout': (self.timeout, None)} + r = super().request('POST', _auth_api_url, **args) + + r.raise_for_status() + r_json=r.json() + self.params.update({'access_token': r_json['id']}) + return + + + def _run(self,info, device): + post_job_url='Network/ibm-q/Groups/open/Projects/main/Jobs' + shots=info['shots'] + nq=info['nq'] + mq=self.backends[device]['nq'] + version=self.backends[device]['version'] + instructions=info['json'] + maxcredit=info['maxCredits'] + + c_label=[] + q_label=[] + for i in range(nq): + c_label.append(['c',i]) + for i in range(mq): + q_label.append(['q',i]) + experiment=[{'header': {'qreg_sizes': [['q', mq]], 'n_qubits': mq, 'memory_slots': nq, 'creg_sizes': [['c', nq]], 'clbit_labels': c_label, 'qubit_labels': q_label, 'name': 'circuit0'}, 'config': {'n_qubits': mq, 'memory_slots': nq}, 'instructions':instructions}] + argument={'data': None, 'json': {'qObject': {'type': 'QASM', 'schema_version': '1.1.0', 'config': {'shots': shots, 'max_credits': maxcredit, 'n_qubits': mq, 'memory_slots': nq, 'memory': False, 'parameter_binds': []}, 'experiments': experiment, 'header': {'backend_version': version, 'backend_name': device}, 'qobj_id': 'e72443f5-7752-4e32-9ac8-156f1f3fee18'}, 'backend': {'name': device}, 'shots': shots}, 'timeout': (self.timeout, None)} + r = super().request('POST', urljoin(_api_url, post_job_url), **argument) + r.raise_for_status() + r_json=r.json() + execution_id = r_json["id"] + return execution_id + + def _get_result(self,device, execution_id, num_retries=3000, + interval=1, verbose=False): + + job_status_url='Network/ibm-q/Groups/open/Projects/main/Jobs/'+execution_id + + if verbose: + print("Waiting for results. [Job ID: {}]".format(execution_id)) + + original_sigint_handler = signal.getsignal(signal.SIGINT) + def _handle_sigint_during_get_result(*_): + raise Exception("Interrupted. The ID of your submitted job is {}." + .format(execution_id)) -_api_url = 'https://quantumexperience.ng.bluemix.net/api/' + try: + signal.signal(signal.SIGINT, _handle_sigint_during_get_result) + for retries in range(num_retries): + + argument={'allow_redirects': True, 'timeout': (self.timeout, None)} + r = super().request('GET', urljoin(_api_url, job_status_url), **argument) + r.raise_for_status() + r_json = r.json() + if r_json['status']=='COMPLETED': + return r_json['qObjectResult']['results'][0] + elif r_json['status']!='RUNNING': + raise Exception("Error while running the code: {}." + .format(r_json['status'])) + time.sleep(interval) + if self.is_online(device) and retries % 60 == 0: + self.get_list_devices() + if not self.is_online(device): + raise DeviceOfflineError("Device went offline. The ID of " + "your submitted job is {}." + .format(execution_id)) + + finally: + if original_sigint_handler is not None: + signal.signal(signal.SIGINT, original_sigint_handler) + + raise Exception("Timeout. The ID of your submitted job is {}." + .format(execution_id)) + +class DeviceTooSmall(Exception): + pass class DeviceOfflineError(Exception): pass +def show_devices(token,verbose=False): """ + Access the list of available devices and their properties (ex: for setup configuration) -def is_online(device): - url = 'Backends/{}/queue/status'.format(device) - r = requests.get(urljoin(_api_url, url)) - return r.json()['state'] + Args: + token (str): IBM quantum experience user password. + verbose (bool): If True, additional information is printed + Return: + (list) list of available devices and their properties + """ + ibmq_session=IBMQ() + ibmq_session._authenticate(token=token) + return ibmq_session.get_list_devices(verbose=verbose) -def retrieve(device, user, password, jobid, num_retries=3000, +def retrieve(device, token, jobid, num_retries=3000, interval=1, verbose=False): """ Retrieves a previously run job by its ID. Args: device (str): Device on which the code was run / is running. - user (str): IBM quantum experience user (e-mail) - password (str): IBM quantum experience password + token (str): IBM quantum experience user password. jobid (str): Id of the job to retrieve + + Return: + (dict) result form the IBMQ server """ - user_id, access_token = _authenticate(user, password) - res = _get_result(device, jobid, access_token, num_retries=num_retries, + ibmq_session=IBMQ() + imbq_session._authenticate(token) + res = imbq_session._get_result(device, jobid, num_retries=num_retries, interval=interval, verbose=verbose) return res -def send(info, device='sim_trivial_2', user=None, password=None, +def send(info, device='ibmq_qasm_simulator',token=None, shots=1, num_retries=3000, interval=1, verbose=False): """ Sends QASM through the IBM API and runs the quantum circuit. Args: - info: Contains QASM representation of the circuit to run. - device (str): Either 'simulator', 'ibmqx4', or 'ibmqx5'. - user (str): IBM quantum experience user. - password (str): IBM quantum experience user password. + info(dict): Contains representation of the circuit to run. + device (str): name of the ibm device. Simulator chosen by default + token (str): IBM quantum experience user password. shots (int): Number of runs of the same circuit to collect statistics. verbose (bool): If True, additional information is printed, such as measurement statistics. Otherwise, the backend simply registers one measurement result (same behavior as the projectq Simulator). + + Return: + (dict) result form the IBMQ server + """ try: - # check if the device is online - if device in ['ibmqx4', 'ibmqx5']: - online = is_online(device) - - if not online: - print("The device is offline (for maintenance?). Use the " - "simulator instead or try again later.") - raise DeviceOfflineError("Device is offline.") + ibmq_session=IBMQ() if verbose: print("- Authenticating...") - user_id, access_token = _authenticate(user, password) + print('TOKEN: '+token) + ibmq_session._authenticate(token) + + # check if the device is online + ibmq_session.get_list_devices(verbose) + online = ibmq_session.is_online(device) + if not online: + print("The device is offline (for maintenance?). Use the " + "simulator instead or try again later.") + raise DeviceOfflineError("Device is offline.") + + # check if the device has enough qubit to run the code + runnable,qmax,qneeded = ibmq_session.can_run_experiment(info,device) + if not runnable: + print("The device is too small ({} qubits available) for the code requested({} qubits needed" + "Try to look for another device with more qubits".format(qmax,qneeded)) + raise DeviceTooSmall("Device is too small.") if verbose: - print("- Running code: {}".format( - json.loads(info)['qasms'][0]['qasm'])) - execution_id = _run(info, device, user_id, access_token, shots) + print("- Running code: {}".format(info)) + execution_id = ibmq_session._run(info, device) if verbose: print("- Waiting for results...") - res = _get_result(device, execution_id, access_token, - num_retries=num_retries, - interval=interval, verbose=verbose) + res = ibmq_session._get_result(device, execution_id,num_retries=num_retries,interval=interval, verbose=verbose) if verbose: print("- Done.") return res @@ -102,93 +247,3 @@ def send(info, device='sim_trivial_2', user=None, password=None, except KeyError as err: print("- Failed to parse response:") print(err) - - -def _authenticate(email=None, password=None): - """ - :param email: - :param password: - :return: - """ - if email is None: - try: - input_fun = raw_input - except NameError: - input_fun = input - email = input_fun('IBM QE user (e-mail) > ') - if password is None: - password = getpass.getpass(prompt='IBM QE password > ') - - r = requests.post(urljoin(_api_url, 'users/login'), - data={"email": email, "password": password}) - r.raise_for_status() - - json_data = r.json() - user_id = json_data['userId'] - access_token = json_data['id'] - - return user_id, access_token - - -def _run(qasm, device, user_id, access_token, shots): - suffix = 'Jobs' - - r = requests.post(urljoin(_api_url, suffix), - data=qasm, - params={"access_token": access_token, - "deviceRunType": device, - "fromCache": "false", - "shots": shots}, - headers={"Content-Type": "application/json"}) - r.raise_for_status() - - r_json = r.json() - execution_id = r_json["id"] - return execution_id - - -def _get_result(device, execution_id, access_token, num_retries=3000, - interval=1, verbose=False): - suffix = 'Jobs/{execution_id}'.format(execution_id=execution_id) - status_url = urljoin(_api_url, 'Backends/{}/queue/status'.format(device)) - - if verbose: - print("Waiting for results. [Job ID: {}]".format(execution_id)) - - original_sigint_handler = signal.getsignal(signal.SIGINT) - - def _handle_sigint_during_get_result(*_): - raise Exception("Interrupted. The ID of your submitted job is {}." - .format(execution_id)) - - try: - signal.signal(signal.SIGINT, _handle_sigint_during_get_result) - - for retries in range(num_retries): - r = requests.get(urljoin(_api_url, suffix), - params={"access_token": access_token}) - r.raise_for_status() - r_json = r.json() - if 'qasms' in r_json: - qasm = r_json['qasms'][0] - if 'result' in qasm and qasm['result'] is not None: - return qasm['result'] - time.sleep(interval) - if device in ['ibmqx4', 'ibmqx5'] and retries % 60 == 0: - r = requests.get(status_url) - r_json = r.json() - if 'state' in r_json and not r_json['state']: - raise DeviceOfflineError("Device went offline. The ID of " - "your submitted job is {}." - .format(execution_id)) - if verbose and 'lengthQueue' in r_json: - print("Currently there are {} jobs queued for execution " - "on {}." - .format(r_json['lengthQueue'], device)) - - finally: - if original_sigint_handler is not None: - signal.signal(signal.SIGINT, original_sigint_handler) - - raise Exception("Timeout. The ID of your submitted job is {}." - .format(execution_id)) diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index 6162fa618..a5393b2e5 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -28,9 +28,7 @@ def no_requests(monkeypatch): monkeypatch.delattr("requests.sessions.Session.request") -_api_url = 'https://quantumexperience.ng.bluemix.net/api/' -_api_url_status = 'https://quantumexperience.ng.bluemix.net/api/' - +_api_url = 'https://api.quantum-computing.ibm.com/api/' def test_send_real_device_online_verbose(monkeypatch): qasms = {'qasms': [{'qasm': 'my qasm'}]} @@ -70,20 +68,21 @@ def raise_for_status(self): pass # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if (args[0] == urljoin(_api_url_status, status_url) and (request_num[0] == 0 or request_num[0] == 3)): request_num[0] += 1 - return MockResponse({"state": True}, 200) + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) # Getting result elif (args[0] == urljoin(_api_url, - "Jobs/{execution_id}".format(execution_id=execution_id)) and + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id)) and kwargs["params"]["access_token"] == access_token and not result_ready[0] and request_num[0] == 3): result_ready[0] = True - return MockResponse({"status": {"id": "NotDone"}}, 200) + request_num[0] += 1 + return MockResponse({"status": {"id": "RUNNING"}}, 200) elif (args[0] == urljoin(_api_url, - "Jobs/{execution_id}".format(execution_id=execution_id)) and + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id)) and kwargs["params"]["access_token"] == access_token and result_ready[0] and request_num[0] == 4): print("state ok") @@ -108,9 +107,8 @@ def raise_for_status(self): pass # Authentication - if (args[0] == urljoin(_api_url, "users/login") and - kwargs["data"]["email"] == email and - kwargs["data"]["password"] == password and + if (args[0] == urljoin(_api_url, "users/loginWithToken") and + kwargs["json"]["apiToken"] == token request_num[0] == 1): request_num[0] += 1 return MockPostResponse({"userId": user_id, "id": access_token}) @@ -129,21 +127,18 @@ def raise_for_status(self): monkeypatch.setattr("requests.get", mocked_requests_get) monkeypatch.setattr("requests.post", mocked_requests_post) # Patch login data - password = 12345 - email = "test@projectq.ch" - monkeypatch.setitem(__builtins__, "input", lambda x: email) - monkeypatch.setitem(__builtins__, "raw_input", lambda x: email) + token = 12345 def user_password_input(prompt): - if prompt == "IBM QE password > ": - return password + if prompt == "IBM QE token > ": + return token monkeypatch.setattr("getpass.getpass", user_password_input) # Code to test: res = _ibm_http_client.send(json_qasm, device="ibmqx4", - user=None, password=None, + token=None, shots=shots, verbose=True) print(res) assert res == result @@ -160,8 +155,8 @@ def json(self): return self.json_data # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' - if args[0] == urljoin(_api_url_status, status_url): + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' + if args[0] == urljoin(_api_url, status_url): return MockResponse({"state": False}, 200) monkeypatch.setattr("requests.get", mocked_requests_get) shots = 1 @@ -170,7 +165,7 @@ def json(self): with pytest.raises(_ibm_http_client.DeviceOfflineError): _ibm_http_client.send(json_qasm, device="ibmqx4", - user=None, password=None, + token=None, shots=shots, verbose=True) @@ -185,9 +180,9 @@ def json(self): def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[0] == urljoin(_api_url_status, status_url): - return MockResponse({"state": True}, 200) + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) def mocked_requests_post(*args, **kwargs): # Test that this error gets caught @@ -196,14 +191,11 @@ def mocked_requests_post(*args, **kwargs): monkeypatch.setattr("requests.get", mocked_requests_get) monkeypatch.setattr("requests.post", mocked_requests_post) # Patch login data - password = 12345 - email = "test@projectq.ch" - monkeypatch.setitem(__builtins__, "input", lambda x: email) - monkeypatch.setitem(__builtins__, "raw_input", lambda x: email) + token = 12345 def user_password_input(prompt): - if prompt == "IBM QE password > ": - return password + if prompt == "IBM QE token > ": + return token monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 @@ -211,24 +203,26 @@ def user_password_input(prompt): name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", - user=None, password=None, + token=None, shots=shots, verbose=True) + + def test_send_that_errors_are_caught2(monkeypatch): - def mocked_requests_get(*args, **kwargs): - class MockResponse: - def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code + class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code - def json(self): - return self.json_data + def json(self): + return self.json_data + def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[0] == urljoin(_api_url_status, status_url): - return MockResponse({"state": True}, 200) + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) def mocked_requests_post(*args, **kwargs): # Test that this error gets caught @@ -237,14 +231,11 @@ def mocked_requests_post(*args, **kwargs): monkeypatch.setattr("requests.get", mocked_requests_get) monkeypatch.setattr("requests.post", mocked_requests_post) # Patch login data - password = 12345 - email = "test@projectq.ch" - monkeypatch.setitem(__builtins__, "input", lambda x: email) - monkeypatch.setitem(__builtins__, "raw_input", lambda x: email) + token = 12345 def user_password_input(prompt): - if prompt == "IBM QE password > ": - return password + if prompt == "IBM QE token > ": + return token monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 @@ -252,24 +243,25 @@ def user_password_input(prompt): name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", - user=None, password=None, + token=None, shots=shots, verbose=True) + def test_send_that_errors_are_caught3(monkeypatch): - def mocked_requests_get(*args, **kwargs): - class MockResponse: - def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code + class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code - def json(self): - return self.json_data + def json(self): + return self.json_data + def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[0] == urljoin(_api_url_status, status_url): - return MockResponse({"state": True}, 200) + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) def mocked_requests_post(*args, **kwargs): # Test that this error gets caught @@ -278,14 +270,11 @@ def mocked_requests_post(*args, **kwargs): monkeypatch.setattr("requests.get", mocked_requests_get) monkeypatch.setattr("requests.post", mocked_requests_post) # Patch login data - password = 12345 - email = "test@projectq.ch" - monkeypatch.setitem(__builtins__, "input", lambda x: email) - monkeypatch.setitem(__builtins__, "raw_input", lambda x: email) + token = 12345 def user_password_input(prompt): - if prompt == "IBM QE password > ": - return password + if prompt == "IBM QE token > ": + return token monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 @@ -293,10 +282,12 @@ def user_password_input(prompt): name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", - user=None, password=None, + token=None, shots=shots, verbose=True) + + def test_timeout_exception(monkeypatch): qasms = {'qasms': [{'qasm': 'my qasm'}]} json_qasm = json.dumps(qasms) @@ -314,14 +305,14 @@ def json(self): def raise_for_status(self): pass - # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' - if args[0] == urljoin(_api_url, status_url): - return MockResponse({"state": True}, 200) - job_url = 'Jobs/{}'.format("123e") + # Accessing status of device. Return device info. + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' + if args[0] == urljoin(_api_url_status, status_url): + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format("123e") if args[0] == urljoin(_api_url, job_url): tries[0] += 1 - return MockResponse({"noqasms": "not done"}, 200) + return MockResponse({"status": {"id": "RUNNING"}}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -340,7 +331,7 @@ def json(self): def raise_for_status(self): pass - login_url = 'users/login' + login_url = 'users/loginWithToken' if args[0] == urljoin(_api_url, login_url): return MockPostResponse({"userId": "1", "id": "12"}) if args[0] == urljoin(_api_url, 'Jobs'): @@ -352,8 +343,8 @@ def raise_for_status(self): with pytest.raises(Exception) as excinfo: _ibm_http_client.send(json_qasm, device="ibmqx4", - user="test", password="test", - shots=1, verbose=False) + token="test", + shots=1,num_retries=10 verbose=False) assert "123e" in str(excinfo.value) # check that job id is in exception assert tries[0] > 0 @@ -376,11 +367,11 @@ def raise_for_status(self): pass # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[0] == urljoin(_api_url, status_url) and request_num[0] < 2: - return MockResponse({"state": True, "lengthQueue": 10}, 200) - elif args[0] == urljoin(_api_url, status_url): - return MockResponse({"state": False}, 200) + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + elif args[0] == urljoin(_api_url, status_url):#ibmqx4 gets disconnected, replaced by ibmqx5 + return MockResponse([{'backend_name': 'ibmqx5', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) job_url = 'Jobs/{}'.format("123e") if args[0] == urljoin(_api_url, job_url): request_num[0] += 1 @@ -403,7 +394,7 @@ def json(self): def raise_for_status(self): pass - login_url = 'users/login' + login_url = 'users/loginWithToken' if args[0] == urljoin(_api_url, login_url): return MockPostResponse({"userId": "1", "id": "12"}) @@ -434,16 +425,16 @@ def raise_for_status(self): pass # Accessing status of device. Return online. - status_url = 'Backends/ibmqx4/queue/status' + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[0] == urljoin(_api_url, status_url): - return MockResponse({"state": True}, 200) - job_url = 'Jobs/{}'.format("123e") + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + job_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/{}'.format("123e") if args[0] == urljoin(_api_url, job_url) and request_num[0] < 1: request_num[0] += 1 - return MockResponse({"noqasms": "not done"}, 200) + return MockResponse({"status": {"id": "RUNNING"}}, 200) elif args[0] == urljoin(_api_url, job_url): - return MockResponse({"qasms": [{'qasm': 'qasm', - 'result': 'correct'}]}, 200) + return MockResponse({"qObjectResult": [{'qasm': 'qasm', + 'result': 'correct'}],"status": {"id": "COMPLETED"}}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -462,7 +453,7 @@ def json(self): def raise_for_status(self): pass - login_url = 'users/login' + login_url = 'users/loginWithToken' if args[0] == urljoin(_api_url, login_url): return MockPostResponse({"userId": "1", "id": "12"}) @@ -470,6 +461,6 @@ def raise_for_status(self): monkeypatch.setattr("requests.post", mocked_requests_post) _ibm_http_client.time.sleep = lambda x: x res = _ibm_http_client.retrieve(device="ibmqx4", - user="test", password="test", + token="test", jobid="123e") assert res == 'correct' diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index df1652b7a..8cf633c08 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -45,9 +45,9 @@ def no_requests(monkeypatch): @pytest.mark.parametrize("single_qubit_gate, is_available", [ - (X, True), (Y, True), (Z, True), (T, True), (Tdag, True), (S, True), - (Sdag, True), (Allocate, True), (Deallocate, True), (Measure, True), - (NOT, True), (Rx(0.5), True), (Ry(0.5), True), (Rz(0.5), True), + (X, False), (Y, False), (Z, False), (T, False), (Tdag, False), (S, False), + (Sdag, False), (Allocate, True), (Deallocate, True), (Measure, True), + (NOT, False), (Rx(0.5), True), (Ry(0.5), True), (Rz(0.5), True), (Barrier, True), (Entangle, False)]) def test_ibm_backend_is_available(single_qubit_gate, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) @@ -175,7 +175,6 @@ def mock_send(*args, **kwargs): qureg = eng.allocate_qureg(3) # entangle the qureg Entangle | qureg - Tdag | qureg[0] Sdag | qureg[0] Barrier | qureg Rx(0.2) | qureg[0] diff --git a/projectq/cengines/_basicmapper.py b/projectq/cengines/_basicmapper.py index 4d4cef177..5fc0f9a81 100644 --- a/projectq/cengines/_basicmapper.py +++ b/projectq/cengines/_basicmapper.py @@ -81,3 +81,7 @@ def add_logical_id(command, old_tags=deepcopy(cmd.tags)): drop_engine_after(self) else: self.send([new_cmd]) + + def receive(self, command_list): + for cmd in command_list: + self._send_cmd_with_mapped_ids(cmd) diff --git a/projectq/cengines/_ibm5qubitmapper.py b/projectq/cengines/_ibm5qubitmapper.py index 7a2659a30..348337fb2 100755 --- a/projectq/cengines/_ibm5qubitmapper.py +++ b/projectq/cengines/_ibm5qubitmapper.py @@ -40,7 +40,7 @@ class IBM5QubitMapper(BasicMapperEngine): **raises an Exception**. """ - def __init__(self): + def __init__(self, connections=None): """ Initialize an IBM 5-qubit mapper compiler engine. @@ -49,6 +49,11 @@ def __init__(self): BasicMapperEngine.__init__(self) self.current_mapping = dict() self._reset() + if connections is None: + self.connections=set([(0, 1), (1, 0), (1, 2), (1, 3), + (2, 1), (3, 1), (3, 4), (4, 3)]) + else: + self.connections=connections def is_available(self, cmd): """ @@ -90,15 +95,15 @@ def _determine_cost(self, mapping): Cost measure taking into account CNOT directionality or None if the circuit cannot be executed given the mapping. """ - from projectq.setups.ibm import ibmqx4_connections as connections + cost = 0 for tpl in self._interactions: ctrl_id = tpl[0] target_id = tpl[1] ctrl_pos = mapping[ctrl_id] target_pos = mapping[target_id] - if not (ctrl_pos, target_pos) in connections: - if (target_pos, ctrl_pos) in connections: + if not (ctrl_pos, target_pos) in self.connections: + if (target_pos, ctrl_pos) in self.connections: cost += self._interactions[tpl] else: return None diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index a5fb2c802..c5aaa3a46 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -13,44 +13,83 @@ # limitations under the License. """ -Defines a setup useful for the IBM QE chip with 5 qubits. - -It provides the `engine_list` for the `MainEngine`, and contains an -AutoReplacer with most of the gate decompositions of ProjectQ, among others -it includes: - - * Controlled z-rotations --> Controlled NOTs and single-qubit rotations - * Toffoli gate --> CNOT and single-qubit gates - * m-Controlled global phases --> (m-1)-controlled phase-shifts - * Global phases --> ignore - * (controlled) Swap gates --> CNOTs and Toffolis - * Arbitrary single qubit gates --> Rz and Ry - * Controlled arbitrary single qubit gates --> Rz, Ry, and CNOT gates - -Moreover, it contains `LocalOptimizers` and a custom mapper for the CNOT -gates. +Defines a setup allowing to compile code for the IBM quantum chips: +->Any 5 qubit devices +->the ibmq online simulator +->the melbourne 15 qubit device +It provides the `engine_list` for the `MainEngine' based on the requested device. +Decompose the circuit into a Rx/Ry/Rz/H/CNOT gate set that will be translated +in the backend in the U1/U2/U3/CX gate set. """ import projectq import projectq.setups.decompositions +from projectq.setups import restrictedgateset +from projectq.ops import (Rx,Ry,Rz,H,CNOT) from projectq.cengines import (TagRemover, LocalOptimizer, AutoReplacer, IBM5QubitMapper, SwapAndCNOTFlipper, - DecompositionRuleSet) + DecompositionRuleSet, + InstructionFilter, + BasicMapperEngine, + GridMapper) +from projectq.backends._ibm._ibm_http_client_v2 import show_devices +def get_engine_list(token=None,device=None): + rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) + #access to the hardware properties via show_devices + #can be extended to take into account gate fidelities, new available gate, etc.. + devices=show_devices(token) + ibm_setup=[] + if device not in devices: + raise DeviceOfflineError('Error when configuring engine list: device ' + 'requested for Backend not connected') + elif devices[device]['nq']==5: + #The requested device is a 5 qubit processor + #Obtain the coupling map specific to the device + coupling_map=devices[device]['coupling_map'] + coupling_map=ListToSet(coupling_map) + Mapper=IBM5QubitMapper(coupling_map) + ibm_setup=[Mapper,SwapAndCNOTFlipper(coupling_map),LocalOptimizer(10)] + elif device=='ibmq_qasm_simulator': + #the 32 qubit online simulator doesn't need a specific mapping for gates. + #can also run wider gateset but this setup keep the restrictedgateset setup by convenience + mapper = BasicMapperEngine() + res=dict() + for i in range(devices[device]['nq']): + res[i]=i + mapper.current_mapping = res + ibm_setup=[mapper] + elif device=='ibmq_16_melbourne': + #Only 15 qubits available on this ibmqx2 unit(in particular qubit 7 on the grid), therefore need custom grid mapping + grid_to_physical = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 15, 8: 14, + 9: 13, 10: 12, 11: 11, 12: 10, 13: 9, 14: 8, 15: 7} + coupling_map=devices[device]['coupling_map'] + coupling_map=ListToSet(coupling_map) + ibm_setup=[GridMapper(2, 8, grid_to_physical),LocalOptimizer(5),SwapAndCNOTFlipper(coupling_map),LocalOptimizer(5)] + else: + #Would be more ergonomic to include the 16qubit device and any other architecture there + raise DeviceNotHandledError('Device not yet fully handled by ProjectQ') -ibmqx4_connections = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) + #Most IBM devices accept U1,U2,U3,CX gates. + #Most gates need to be decomposed into a subset that is manually converted + #in the backend (until the implementation of the U1,U2,U3) + setup=restrictedgateset.get_engine_list(one_qubit_gates=(Rx,Ry,Rz,H), + two_qubit_gates=(CNOT,)) + setup.extend(ibm_setup) + return setup +class DeviceOfflineError(Exception): + pass -def get_engine_list(): - rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - return [TagRemover(), - LocalOptimizer(10), - AutoReplacer(rule_set), - TagRemover(), - IBM5QubitMapper(), - SwapAndCNOTFlipper(ibmqx4_connections), - LocalOptimizer(10)] +class DeviceNotHandledError(Exception): + pass + +def ListToSet(coupling_list): + result=[] + for el in coupling_list: + result.append(tuple(el)) + return set(result) \ No newline at end of file diff --git a/projectq/setups/ibm_test.py b/projectq/setups/ibm_test.py index 598b949cb..b66ca4efb 100644 --- a/projectq/setups/ibm_test.py +++ b/projectq/setups/ibm_test.py @@ -20,10 +20,10 @@ def test_ibm_cnot_mapper_in_cengines(): import projectq.setups.ibm - found = 0 - for engine in projectq.setups.ibm.get_engine_list(): - if isinstance(engine, IBM5QubitMapper): - found |= 1 - if isinstance(engine, SwapAndCNOTFlipper): - found |= 2 - assert found == 3 + engines_5qb=projectq.setups.ibm.get_engine_list(device='ibmq_burlington') + engines_15qb=projectq.setups.ibm.get_engine_list(device='ibmq_16_melbourne') + engines_simulator=projectq.setups.ibm.get_engine_list(device='ibmq_qasm_simulator') + assert len(engines_5qb)==15 + assert len(engines_15qb)==16 + assert len(engines_simulator)==13 + From 88e0c63646dd5d65d34aa962e017776ee7650b5e Mon Sep 17 00:00:00 2001 From: dbretaud Date: Thu, 2 Jan 2020 14:30:23 +0000 Subject: [PATCH 23/43] minor fixes --- projectq/backends/_ibm/_ibm.py | 4 ++-- projectq/backends/_ibm/_ibm_http_client.py | 3 ++- projectq/setups/ibm.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index 20dc2a645..874cd2d3e 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -36,10 +36,10 @@ Barrier, FlushGate) -from ._ibm_http_client_v2 import send, retrieve +from ._ibm_http_client import send, retrieve -class IBMBackend_v2(BasicEngine): +class IBMBackend(BasicEngine): """ The IBM Backend class, which stores the circuit, transforms it to JSON, and sends the circuit through the IBM API. diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 8de1ab6bc..381eab36c 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -155,7 +155,8 @@ class DeviceTooSmall(Exception): class DeviceOfflineError(Exception): pass -def show_devices(token,verbose=False): """ +def show_devices(token,verbose=False): + """ Access the list of available devices and their properties (ex: for setup configuration) Args: diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index c5aaa3a46..f0a8ea45e 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -36,7 +36,7 @@ InstructionFilter, BasicMapperEngine, GridMapper) -from projectq.backends._ibm._ibm_http_client_v2 import show_devices +from projectq.backends._ibm._ibm_http_client import show_devices def get_engine_list(token=None,device=None): rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) From 34f280e3a69be3191946629d201c38cd518a95a0 Mon Sep 17 00:00:00 2001 From: dbretaud Date: Thu, 2 Jan 2020 14:47:56 +0000 Subject: [PATCH 24/43] update on the ibm example --- examples/ibm.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/examples/ibm.py b/examples/ibm.py index 05e042230..37ba4e0d7 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -2,9 +2,10 @@ from projectq.backends import IBMBackend from projectq.ops import Measure, Entangle, All from projectq import MainEngine +import getpass -def run_entangle(eng, num_qubits=5): +def run_entangle(eng, num_qubits=3): """ Runs an entangling operation on the provided compiler engine. @@ -37,9 +38,19 @@ def run_entangle(eng, num_qubits=5): if __name__ == "__main__": + #devices commonly available : + #ibmq_16_melbourne (15 qubit) + #ibmq_essex (5 qubit) + #ibmq_qasm_simulator (32 qubits) + device = None #replace by the IBM device name you want to use + token = None #replace by the token given by IBMQ + if token is None: + token = getpass.getpass(prompt='IBM Q token > ') + if device is None: + token = getpass.getpass(prompt='IBM device > ') # create main compiler engine for the IBM back-end - eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024, - verbose=False, device='ibmqx4'), - engine_list=projectq.setups.ibm.get_engine_list()) + eng = MainEngine(IBMBackend(use_hardware=True, token=token num_runs=1024, + verbose=False, device=device), + engine_list=projectq.setups.ibm.get_engine_list(token=token, device=device)) # run the circuit and print the result print(run_entangle(eng)) From 0413e10e244ce54da37fef8be877c3b3182890cd Mon Sep 17 00:00:00 2001 From: dbretaud Date: Tue, 7 Jan 2020 23:39:29 +0000 Subject: [PATCH 25/43] minor fixes; revert rotation gates to [0;4pi) --- projectq/cengines/_optimize.py | 27 ++++++++--------- projectq/ops/_basics.py | 37 +++++++++++++++-------- projectq/ops/_basics_test.py | 5 ++- projectq/ops/_command.py | 8 ++--- projectq/ops/_command_test.py | 8 +++-- projectq/setups/trapped_ion_decomposer.py | 7 +++++ 6 files changed, 54 insertions(+), 38 deletions(-) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 758aeaccb..0c9765288 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -131,21 +131,18 @@ def _optimize(self, idx, lim=None): while i < limit - 1: # can be dropped if the gate is equivalent to an identity gate - try: - 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 - except: - pass + 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/ops/_basics.py b/projectq/ops/_basics.py index bd777aecd..eb10ea74b 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -226,7 +226,12 @@ def __ne__(self, other): def __str__(self): raise NotImplementedError('This gate does not implement __str__.') - def to_String(self,symbols): + 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): @@ -325,35 +330,41 @@ class BasicRotationGate(BasicGate): A rotation gate has a continuous parameter (the angle), labeled 'angle' / self.angle. Its inverse is the same gate with the negated argument. Rotation gates of the same class can be merged by adding the angles. - The continuous parameter is modulo 2 * pi, self.angle is in the interval - [0, 2 * pi). + The continuous parameter is modulo 4 * pi, self.angle is in the interval + [0, 4 * pi). """ def __init__(self, angle): """ Initialize a basic rotation gate. Args: - angle (float): Angle of rotation (saved modulo 2 * pi) + angle (float): Angle of rotation (saved modulo 4 * pi) """ BasicGate.__init__(self) - rounded_angle = round(float(angle) % (2. * math.pi), ANGLE_PRECISION) - if rounded_angle > 2 * math.pi - ANGLE_TOLERANCE: + rounded_angle = round(float(angle) % (4. * math.pi), ANGLE_PRECISION) + if rounded_angle > 4 * math.pi - ANGLE_TOLERANCE: rounded_angle = 0. self.angle = rounded_angle - def __str__(self,symbols=False): - return self.to_String() - - def to_String(self,symbols=False): + def __str__(self): """ Return the string representation of a BasicRotationGate. Returns the class name and the angle as .. code-block:: python - + [CLASSNAME]([ANGLE]) """ + return self.to_string() + + def to_string(self,symbols=False): + """ + Return the string representation of a BasicRotationGate. + + Args: + symbols: 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: @@ -380,7 +391,7 @@ def get_inverse(self): if self.angle == 0: return self.__class__(0) else: - return self.__class__(-self.angle + 2 * math.pi) + return self.__class__(-self.angle + 4 * math.pi) def get_merged(self, other): """ @@ -420,7 +431,7 @@ def is_identity(self): """ Return True if the gate is equivalent to an Identity gate """ - return self.angle == 0. or self.angle==2*math.pi + return self.angle == 0. or self.angle==4*math.pi class BasicPhaseGate(BasicGate): diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index 61a229fd2..a254b5b97 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -165,9 +165,8 @@ def test_basic_rotation_gate_init(input_angle, modulo_angle): def test_basic_rotation_gate_str(): basic_rotation_gate = _basics.BasicRotationGate(math.pi) assert str(basic_rotation_gate) == "BasicRotationGate(3.14159265359)" - basic_rotation_gate = _basics.BasicRotationGate(0.5*math.pi) - assert basic_rotation_gate.to_String(symbols=True) == "BasicRotationGate(0.5π)" - + assert basic_rotation_gate.to_string(symbols=True) == "BasicRotationGate(0.5π)" + assert basic_rotation_gate.to_string(symbols=False) == "BasicRotationGate(3.14159265359)" def test_basic_rotation_tex_str(): basic_rotation_gate = _basics.BasicRotationGate(0.5*math.pi) diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 1ea85b6f4..59dff1a4e 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -305,10 +305,10 @@ def __eq__(self, other): def __ne__(self, other): return not self.__eq__(other) - def __str__(self,symbols=False): - return self.to_String() + def __str__(self): + return self.to_string() - def to_String(self,symbols=False): + def to_string(self,symbols=False): """ Get string representation of this Command object. """ @@ -326,4 +326,4 @@ def to_String(self,symbols=False): qstring += ", " qstring = qstring[:-2] + " )" cstring = "C" * len(ctrlqubits) - return cstring + self.gate.to_String(symbols) + " | " + qstring + return cstring + self.gate.to_string(symbols) + " | " + qstring diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index 8db4b75a8..0f6963ae3 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -251,12 +251,14 @@ def test_command_str(): cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) assert str(cmd2) == "Rx(1.570796326795‬) | Qureg[0]" -def test_command_to_String(): +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) - assert cmd.to_String(symbols=True) == "CRx(0.5π) | ( Qureg[1], Qureg[0] )" + assert cmd.to_string(symbols=True) == "CRx(0.5π) | ( Qureg[1], Qureg[0] )" + assert cmd.to_string(symbols=False) == "CRx(1.570796326795‬) | ( Qureg[1], Qureg[0] )" cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) - assert cmd2.to_String(symbols=True) == "Rx(0.5π) | Qureg[0]" \ No newline at end of file + assert cmd2.to_string(symbols=True) == "Rx(0.5π) | Qureg[0]" + assert cmd2.to_string(symbols=False) == "Rx(1.570796326795‬) | Qureg[0]" \ No newline at end of file diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index dc9c0c893..0f1a3241e 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -79,6 +79,9 @@ def chooser_Ry_reducer(cmd,decomposition_list): pass if name=='cnot2rxx': idx = [qb.id for qb in cmd.control_qubits] # index of the qubit + assert len(idx)==1 # this should be a 2 qubit gate, therefore with 1 control qubit + idx=idx[0] + print(idx) if idx in prev_Ry_sign: if prev_Ry_sign[idx]<=0: # If the previous qubit had Ry(-pi/2) @@ -103,6 +106,8 @@ def chooser_Ry_reducer(cmd,decomposition_list): elif name=='h2rx': # Block operates similar to 'cnot2rxx' case idx = [qb.id for qb in cmd.qubits[0]] + assert len(idx)==1 # this should be a single qubit gate + idx=idx[0] if idx not in prev_Ry_sign: prev_Ry_sign[idx]=+1 return decomp_rule['M'] @@ -115,6 +120,8 @@ def chooser_Ry_reducer(cmd,decomposition_list): elif name=='rz2rx': # Block operates similar to 'cnot2rxx' case idx = [qb.id for qb in cmd.qubits[0]] + assert len(idx)==1 # this should be a single qubit gate + idx=idx[0] if idx not in prev_Ry_sign: prev_Ry_sign[idx]=-1 return decomp_rule['M'] From 8ea5a5c030c4317ac2a4e0e0b070424c29ccb68e Mon Sep 17 00:00:00 2001 From: dbretaud Date: Fri, 10 Jan 2020 17:20:38 +0000 Subject: [PATCH 26/43] fix comments --- projectq/setups/decompositions/cnot2rxx.py | 1 + projectq/setups/decompositions/rz2rx.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py index 15c51a825..30c888584 100644 --- a/projectq/setups/decompositions/cnot2rxx.py +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -48,6 +48,7 @@ def _decompose_cnot2rxx_P(cmd): 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 diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index 08f58089f..6b4a55ef7 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -57,7 +57,7 @@ def _decompose_rz2rx_M(cmd): Uncompute(eng) def _recognize_RzNoCtrl(cmd): - """ For efficiency reasons only if no control qubits.""" + """ 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 From 197e93cc1a147f326cb051f71a50e7d8628b629e Mon Sep 17 00:00:00 2001 From: dbretaud Date: Tue, 14 Jan 2020 00:01:59 +0000 Subject: [PATCH 27/43] fix mapper choice for simulator. added comments --- projectq/setups/ibm.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index f0a8ea45e..c84c55a55 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -34,7 +34,7 @@ SwapAndCNOTFlipper, DecompositionRuleSet, InstructionFilter, - BasicMapperEngine, + ManualMapper, GridMapper) from projectq.backends._ibm._ibm_http_client import show_devices @@ -56,27 +56,28 @@ def get_engine_list(token=None,device=None): ibm_setup=[Mapper,SwapAndCNOTFlipper(coupling_map),LocalOptimizer(10)] elif device=='ibmq_qasm_simulator': #the 32 qubit online simulator doesn't need a specific mapping for gates. - #can also run wider gateset but this setup keep the restrictedgateset setup by convenience - mapper = BasicMapperEngine() - res=dict() - for i in range(devices[device]['nq']): - res[i]=i - mapper.current_mapping = res + #can also run wider gateset but this setup keep the restrictedgateset setup for + #coherence + mapper = ManualMapper() ibm_setup=[mapper] elif device=='ibmq_16_melbourne': - #Only 15 qubits available on this ibmqx2 unit(in particular qubit 7 on the grid), therefore need custom grid mapping + #Only 15 qubits available on this ibmqx2 unit(in particular qubit 7 on the grid), + #therefore need custom grid mapping grid_to_physical = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 15, 8: 14, 9: 13, 10: 12, 11: 11, 12: 10, 13: 9, 14: 8, 15: 7} coupling_map=devices[device]['coupling_map'] coupling_map=ListToSet(coupling_map) ibm_setup=[GridMapper(2, 8, grid_to_physical),LocalOptimizer(5),SwapAndCNOTFlipper(coupling_map),LocalOptimizer(5)] else: - #Would be more ergonomic to include the 16qubit device and any other architecture there + #if there is an online device not handled into ProjectQ it's not too bad, + #the engine_list can be constructed manually with the appropriate mapper + #and the 'coupling_map' parameter raise DeviceNotHandledError('Device not yet fully handled by ProjectQ') #Most IBM devices accept U1,U2,U3,CX gates. #Most gates need to be decomposed into a subset that is manually converted #in the backend (until the implementation of the U1,U2,U3) + #available gates decomposable now for U1,U2,U3: Rx,Ry,Rz and H setup=restrictedgateset.get_engine_list(one_qubit_gates=(Rx,Ry,Rz,H), two_qubit_gates=(CNOT,)) setup.extend(ibm_setup) From 03daf7915ce663f8dc79975ba87dabb4534272e6 Mon Sep 17 00:00:00 2001 From: dbretaud Date: Thu, 16 Jan 2020 01:50:56 +0000 Subject: [PATCH 28/43] minor fixes with results, mapper and testing files. Improvement of get_probabilities --- projectq/backends/_ibm/_ibm.py | 40 +++--- projectq/backends/_ibm/_ibm_http_client.py | 64 +++++++-- projectq/backends/_ibm/_ibm_test.py | 151 +++++++++++++-------- projectq/setups/ibm.py | 14 +- 4 files changed, 173 insertions(+), 96 deletions(-) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index 874cd2d3e..4adbdce43 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -136,7 +136,6 @@ def _store(self, cmd): self._allocated_qubits = set() gate = cmd.gate - if gate == Allocate: self._allocated_qubits.add(cmd.qubits[0][0].id) return @@ -187,15 +186,8 @@ def _store(self, cmd): self.qasm += "\nu2(0,pi/2) q[{}];".format(qb_pos) self._json.append({'qubits': [qb_pos], 'name': 'u2','params': [0, 3.141592653589793]}) else: - assert get_control_count(cmd) == 0 - if str(gate) in self._gate_names: - gate_str = self._gate_names[str(gate)] - else: - gate_str = str(gate).lower() - - qb_pos = cmd.qubits[0][0].id - self.qasm += "\n{} q[{}];".format(gate_str, qb_pos) - self._json.append({'qubits': [qb_pos], 'name': gate_str}) + raise Exception('Command not authorized. You should run the circuit with the appropriate ibm setup.' + 'Forbidden command: '+str(cmd)) def _logical_to_physical(self, qb_id): """ @@ -217,6 +209,8 @@ def _logical_to_physical(self, qb_id): def get_probabilities(self, qureg): """ Return the list of basis states with corresponding probabilities. + If input qureg is a subset of the register used for the experiment, + then returns the projected probabilities over the other states. The measured bits are ordered according to the supplied quantum register, i.e., the left-most bit in the state-string corresponds to @@ -242,13 +236,16 @@ def get_probabilities(self, qureg): raise RuntimeError("Please, run the circuit first!") probability_dict = dict() - for state in self._probabilities: mapped_state = ['0'] * len(qureg) for i in range(len(qureg)): mapped_state[i] = state[self._logical_to_physical(qureg[i].id)] probability = self._probabilities[state] - probability_dict["".join(mapped_state)] = probability + mapped_state = "".join(mapped_state) + if mapped_state not in probability_dict: + probability_dict[mapped_state] = probability + else: + probability_dict[mapped_state] += probability return probability_dict @@ -269,13 +266,13 @@ def _run(self): if self.qasm == "": return - max_qubit_id = max(self._allocated_qubits) + max_qubit_id = max(self._allocated_qubits) + 1 qasm = ("\ninclude \"qelib1.inc\";\nqreg q[{nq}];\ncreg c[{nq}];" + - self.qasm).format(nq=max_qubit_id + 1) + self.qasm).format(nq=max_qubit_id) info = {} info['qasms'] = [{'qasm': qasm}] info['json']=self._json - info['nq']=max_qubit_id + 1 + info['nq']=max_qubit_id info['shots'] = self._num_runs info['maxCredits'] = 10 @@ -295,7 +292,6 @@ def _run(self): num_retries=self._num_retries, interval=self._interval, verbose=self._verbose) - counts = res['data']['counts'] # Determine random outcome P = random.random() @@ -304,9 +300,10 @@ def _run(self): length=len(self._measured_ids) for state in counts: probability = counts[state] * 1. / self._num_runs - state=state.split('x')[1] - state="{0:b}".format(int(state)) - state=state.zfill(length) + state="{0:b}".format(int(state,0)) + state=state.zfill(max_qubit_id) + #states in ibmq are right-ordered, so need to reverse state string + state=state[::-1] p_sum += probability star = "" if p_sum >= P and measured == "": @@ -345,8 +342,3 @@ def receive(self, command_list): self._run() self._reset() - """ - Mapping of gate names from our gate objects to the IBM QASM representation. - """ - _gate_names = {str(Tdag): "tdg", - str(Sdag): "sdg"} diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 381eab36c..0d794fd27 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -13,7 +13,9 @@ # limitations under the License. # helpers to run the jsonified gate sequence on ibm quantum experience server -# api documentation is at https://qcwi-staging.mybluemix.net/explorer/ +# api documentation does not exist and has to be deduced from the qiskit code source +# at: https://github.com/Qiskit/qiskit-ibmq-provider + import requests import getpass import json @@ -25,6 +27,7 @@ _auth_api_url = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' _api_url = 'https://api.quantum-computing.ibm.com/api/' + CLIENT_APPLICATION = 'ibmqprovider/0.4.4'#TODO: call to get the API version automatically @@ -35,10 +38,19 @@ def __init__(self): self.timeout=5.0 def get_list_devices(self,verbose=False): + """ + Get the list of available IBM backends with their properties + + Args: + verbose (bool): print the returned dictionnary if True + Returns: + (dict) backends dictionary by name device, containing the qubit size 'nq', + the coupling map 'coupling_map' as well as the device + version 'version' + """ list_device_url='Network/ibm-q/Groups/open/Projects/main/devices/v/1' argument={'allow_redirects': True, 'timeout': (self.timeout, None)} r = super().request('GET', urljoin(_api_url, list_device_url), **argument) - r.raise_for_status() r_json=r.json() self.backends=dict() @@ -50,6 +62,14 @@ def get_list_devices(self,verbose=False): return self.backends def is_online(self,device): + """ + check if the device is in the list of available IBM backends + + Args: + device (str): name of the device to check + Returns: + (bool) True if device is available, False otherwise + """ return device in self.backends def can_run_experiment(self,info,device): @@ -59,8 +79,11 @@ def can_run_experiment(self,info,device): Args: info (dict): dictionary sent by the backend containing the code to run device (str): name of the ibm device to use - :return: - (bool): True if device is big enough, False otherwise + Returns: + (tuple): (bool) True if device is big enough, False otherwise + (int) maximum number of qubit available on the device + (int) number of qubit needed for the circuit + """ nb_qubit_max=self.backends[device]['nq'] nb_qubit_needed=info['nq'] @@ -68,8 +91,9 @@ def can_run_experiment(self,info,device): def _authenticate(self,token=None): """ - :param token: - :return: + Args: + token (str): IBM quantum experience user API token. + Returns: """ if token is None: token = getpass.getpass(prompt='IBM Q token > ') @@ -98,8 +122,24 @@ def _run(self,info, device): c_label.append(['c',i]) for i in range(mq): q_label.append(['q',i]) - experiment=[{'header': {'qreg_sizes': [['q', mq]], 'n_qubits': mq, 'memory_slots': nq, 'creg_sizes': [['c', nq]], 'clbit_labels': c_label, 'qubit_labels': q_label, 'name': 'circuit0'}, 'config': {'n_qubits': mq, 'memory_slots': nq}, 'instructions':instructions}] - argument={'data': None, 'json': {'qObject': {'type': 'QASM', 'schema_version': '1.1.0', 'config': {'shots': shots, 'max_credits': maxcredit, 'n_qubits': mq, 'memory_slots': nq, 'memory': False, 'parameter_binds': []}, 'experiments': experiment, 'header': {'backend_version': version, 'backend_name': device}, 'qobj_id': 'e72443f5-7752-4e32-9ac8-156f1f3fee18'}, 'backend': {'name': device}, 'shots': shots}, 'timeout': (self.timeout, None)} + experiment=[{'header': {'qreg_sizes': [['q', mq]], 'n_qubits': mq, + 'memory_slots': nq, 'creg_sizes': [['c', nq]], + 'clbit_labels': c_label, 'qubit_labels': q_label, + 'name': 'circuit0'}, + 'config': {'n_qubits': mq, 'memory_slots': nq}, + 'instructions':instructions}] + #Note: qobj_id is not necessary in projectQ, so fixed string for now + argument={'data': None, + 'json': {'qObject': {'type': 'QASM', 'schema_version': '1.1.0', + 'config': {'shots': shots, 'max_credits': maxcredit, + 'n_qubits': mq, 'memory_slots': nq, + 'memory': False, 'parameter_binds': []}, + 'experiments': experiment, + 'header': {'backend_version': version, + 'backend_name': device}, + 'qobj_id': 'e72443f5-7752-4e32-9ac8-156f1f3fee18'}, + 'backend': {'name': device}, 'shots': shots}, + 'timeout': (self.timeout, None)} r = super().request('POST', urljoin(_api_url, post_job_url), **argument) r.raise_for_status() r_json=r.json() @@ -160,7 +200,7 @@ def show_devices(token,verbose=False): Access the list of available devices and their properties (ex: for setup configuration) Args: - token (str): IBM quantum experience user password. + token (str): IBM quantum experience user API token. verbose (bool): If True, additional information is printed Return: @@ -177,7 +217,7 @@ def retrieve(device, token, jobid, num_retries=3000, Args: device (str): Device on which the code was run / is running. - token (str): IBM quantum experience user password. + token (str): IBM quantum experience user API token. jobid (str): Id of the job to retrieve Return: @@ -198,7 +238,7 @@ def send(info, device='ibmq_qasm_simulator',token=None, Args: info(dict): Contains representation of the circuit to run. device (str): name of the ibm device. Simulator chosen by default - token (str): IBM quantum experience user password. + token (str): IBM quantum experience user API token. shots (int): Number of runs of the same circuit to collect statistics. verbose (bool): If True, additional information is printed, such as measurement statistics. Otherwise, the backend simply registers @@ -213,7 +253,7 @@ def send(info, device='ibmq_qasm_simulator',token=None, if verbose: print("- Authenticating...") - print('TOKEN: '+token) + print('user API token: '+token) ibmq_session._authenticate(token) # check if the device is online diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index 8cf633c08..90fb094db 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -17,7 +17,7 @@ import pytest import json -import projectq.setups.decompositions +import projectq.setups.restrictedgateset from projectq import MainEngine from projectq.backends._ibm import _ibm from projectq.cengines import (TagRemover, @@ -29,9 +29,7 @@ DecompositionRuleSet) from projectq.ops import (All, Allocate, Barrier, Command, Deallocate, Entangle, Measure, NOT, Rx, Ry, Rz, S, Sdag, T, Tdag, - X, Y, Z) - -from projectq.setups.ibm import ibmqx4_connections + X, Y, Z, H) # Insure that no HTTP request can be made in all tests in this module @@ -40,12 +38,8 @@ def no_requests(monkeypatch): monkeypatch.delattr("requests.sessions.Session.request") -_api_url = 'https://quantumexperience.ng.bluemix.net/api/' -_api_url_status = 'https://quantumexperience.ng.bluemix.net/api/' - - @pytest.mark.parametrize("single_qubit_gate, is_available", [ - (X, False), (Y, False), (Z, False), (T, False), (Tdag, False), (S, False), + (X, False), (Y, False), (Z, False), (H, True), (T, False), (Tdag, False), (S, False), (Sdag, False), (Allocate, True), (Deallocate, True), (Measure, True), (NOT, False), (Rx(0.5), True), (Ry(0.5), True), (Rz(0.5), True), (Barrier, True), (Entangle, False)]) @@ -84,13 +78,16 @@ def test_ibm_sent_error(monkeypatch): def mock_send(*args, **kwargs): raise TypeError monkeypatch.setattr(_ibm, "send", mock_send) - backend = _ibm.IBMBackend(verbose=True) + mapper = BasicMapperEngine() + res=dict() + for i in range(4): + res[i]=i + mapper.current_mapping = res eng = MainEngine(backend=backend, - engine_list=[IBM5QubitMapper(), - SwapAndCNOTFlipper(set())]) + engine_list=[mapper]) qubit = eng.allocate_qubit() - X | qubit + Rx(math.pi) | qubit with pytest.raises(Exception): qubit[0].__del__() eng.flush() @@ -103,23 +100,38 @@ def mock_send(*args, **kwargs): def test_ibm_retrieve(monkeypatch): # patch send def mock_retrieve(*args, **kwargs): - return {'date': '2017-01-19T14:28:47.622Z', - 'data': {'time': 14.429004907608032, 'counts': {'00111': 396, - '00101': 27, - '00000': 601}, - 'qasm': ('...')}} + return {'data': {'counts': {'0x0': 504, '0x2': 8, '0xc': 6, '0xe': 482}}, + 'header': {'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]], + 'creg_sizes': [['c', 4]], + 'memory_slots': 4, + 'n_qubits': 32, + 'name': 'circuit0', + 'qreg_sizes': [['q', 32]], + 'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3], ['q', 4], ['q', 5], + ['q', 6], ['q', 7], ['q', 8], ['q', 9], ['q', 10], ['q', 11], + ['q', 12], ['q', 13], ['q', 14], ['q', 15], ['q', 16], + ['q', 17], ['q', 18], ['q', 19], ['q', 20], ['q', 21], + ['q', 22], ['q', 23], ['q', 24], ['q', 25], ['q', 26], + ['q', 27], ['q', 28], ['q', 29], ['q', 30], ['q', 31]]}, + 'metadata': {'measure_sampling': True, 'method': 'statevector', 'parallel_shots': 1, + 'parallel_state_update': 16}, + 'seed_simulator': 465435780, + 'shots': 1000, + 'status': 'DONE', + 'success': True, + 'time_taken': 0.0045786460000000005} monkeypatch.setattr(_ibm, "retrieve", mock_retrieve) backend = _ibm.IBMBackend(retrieve_execution="ab1s2") - rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - connectivity = set([(1, 2), (2, 4), (0, 2), (3, 2), (4, 3), (0, 1)]) - engine_list = [TagRemover(), - LocalOptimizer(10), - AutoReplacer(rule_set), - TagRemover(), - IBM5QubitMapper(), - SwapAndCNOTFlipper(connectivity), - LocalOptimizer(10)] - eng = MainEngine(backend=backend, engine_list=engine_list) + mapper = BasicMapperEngine() + res=dict() + for i in range(4): + res[i]=i + mapper.current_mapping = res + ibm_setup=[mapper] + setup=restrictedgateset.get_engine_list(one_qubit_gates=(Rx,Ry,Rz,H), + two_qubit_gates=(CNOT,)) + setup.extend(ibm_setup) + eng = MainEngine(backend=backend, engine_list=setup) unused_qubit = eng.allocate_qubit() qureg = eng.allocate_qureg(3) # entangle the qureg @@ -134,47 +146,76 @@ def mock_retrieve(*args, **kwargs): # run the circuit eng.flush() prob_dict = eng.backend.get_probabilities([qureg[0], qureg[2], qureg[1]]) - assert prob_dict['111'] == pytest.approx(0.38671875) - assert prob_dict['101'] == pytest.approx(0.0263671875) + assert prob_dict['111'] == pytest.approx(0.482) + assert prob_dict['011'] == pytest.approx(0.006) def test_ibm_backend_functional_test(monkeypatch): - correct_info = ('{"qasms": [{"qasm": "\\ninclude \\"qelib1.inc\\";' - '\\nqreg q[3];\\ncreg c[3];\\nh q[2];\\ncx q[2], q[0];' - '\\ncx q[2], q[1];\\ntdg q[2];\\nsdg q[2];' - '\\nbarrier q[2], q[0], q[1];' - '\\nu3(0.2, -pi/2, pi/2) q[2];\\nmeasure q[2] -> ' - 'c[2];\\nmeasure q[0] -> c[0];\\nmeasure q[1] -> c[1];"}]' - ', "shots": 1024, "maxCredits": 5, "backend": {"name": ' - '"simulator"}}') + correct_info = {'qasms': [{'qasm': '\ninclude "qelib1.inc";\nqreg q[4];\ncreg c[4];\nu2(0,pi/2) q[1];' + '\ncx q[1], q[2];\ncx q[1], q[3];\nu3(6.28318530718, 0, 0) q[1];' + '\nu1(11.780972450962) q[1];\nu3(6.28318530718, 0, 0) q[1];' + '\nu1(10.995574287564) q[1];\nu3(0.2, -pi/2, pi/2) q[1];' + '\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];'}], + 'json': [{'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793]}, + {'qubits': [1, 2], 'name': 'cx'}, {'qubits': [1, 3], 'name': 'cx'}, + {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, + {'qubits': [1], 'name': 'u1', 'params': [11.780972450962]}, + {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, + {'qubits': [1], 'name': 'u1', 'params': [10.995574287564]}, + {'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966]}, + {'qubits': [1], 'name': 'measure', 'memory': [1]}, + {'qubits': [2], 'name': 'measure', 'memory': [2]}, + {'qubits': [3], 'name': 'measure', 'memory': [3]}], + 'nq': 4, + 'shots': 1000, + 'maxCredits': 10, + 'backend': {'name': 'ibmq_qasm_simulator'}} def mock_send(*args, **kwargs): assert json.loads(args[0]) == json.loads(correct_info) - return {'date': '2017-01-19T14:28:47.622Z', - 'data': {'time': 14.429004907608032, 'counts': {'00111': 396, - '00101': 27, - '00000': 601}, - 'qasm': ('...')}} + return {'data': {'counts': {'0x0': 504, '0x2': 8, '0xc': 6, '0xe': 482}}, + 'header': {'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]], + 'creg_sizes': [['c', 4]], + 'memory_slots': 4, + 'n_qubits': 32, + 'name': 'circuit0', + 'qreg_sizes': [['q', 32]], + 'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3], ['q', 4], ['q', 5], + ['q', 6], ['q', 7], ['q', 8], ['q', 9], ['q', 10], ['q', 11], + ['q', 12], ['q', 13], ['q', 14], ['q', 15], ['q', 16], + ['q', 17], ['q', 18], ['q', 19], ['q', 20], ['q', 21], + ['q', 22], ['q', 23], ['q', 24], ['q', 25], ['q', 26], + ['q', 27], ['q', 28], ['q', 29], ['q', 30], ['q', 31]]}, + 'metadata': {'measure_sampling': True, 'method': 'statevector', 'parallel_shots': 1, + 'parallel_state_update': 16}, + 'seed_simulator': 465435780, + 'shots': 1000, + 'status': 'DONE', + 'success': True, + 'time_taken': 0.0045786460000000005} monkeypatch.setattr(_ibm, "send", mock_send) backend = _ibm.IBMBackend(verbose=True) # no circuit has been executed -> raises exception with pytest.raises(RuntimeError): backend.get_probabilities([]) - rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - - engine_list = [TagRemover(), - LocalOptimizer(10), - AutoReplacer(rule_set), - TagRemover(), - IBM5QubitMapper(), - SwapAndCNOTFlipper(ibmqx4_connections), - LocalOptimizer(10)] - eng = MainEngine(backend=backend, engine_list=engine_list) + mapper = BasicMapperEngine() + res=dict() + for i in range(3): + res[i]=i + mapper.current_mapping = res + ibm_setup=[mapper] + setup=restrictedgateset.get_engine_list(one_qubit_gates=(Rx,Ry,Rz,H), + two_qubit_gates=(CNOT,)) + setup.extend(ibm_setup) + eng = MainEngine(backend=backend, engine_list=setup) + #4 qubits circuit is run, but first is unused to test ability for get_probability + #to return the correct values for a subset of the total register unused_qubit = eng.allocate_qubit() qureg = eng.allocate_qureg(3) # entangle the qureg Entangle | qureg + Tdag | qureg[0] Sdag | qureg[0] Barrier | qureg Rx(0.2) | qureg[0] @@ -184,8 +225,8 @@ def mock_send(*args, **kwargs): # run the circuit eng.flush() prob_dict = eng.backend.get_probabilities([qureg[0], qureg[2], qureg[1]]) - assert prob_dict['111'] == pytest.approx(0.38671875) - assert prob_dict['101'] == pytest.approx(0.0263671875) + assert prob_dict['111'] == pytest.approx(0.482) + assert prob_dict['011'] == pytest.approx(0.006) with pytest.raises(RuntimeError): eng.backend.get_probabilities(eng.allocate_qubit()) diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index c84c55a55..154cc2a42 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -26,20 +26,18 @@ import projectq import projectq.setups.decompositions from projectq.setups import restrictedgateset -from projectq.ops import (Rx,Ry,Rz,H,CNOT) +from projectq.ops import (Rx, Ry, Rz, H, CNOT) from projectq.cengines import (TagRemover, LocalOptimizer, AutoReplacer, IBM5QubitMapper, SwapAndCNOTFlipper, - DecompositionRuleSet, InstructionFilter, - ManualMapper, + BasicMapperEngine, GridMapper) from projectq.backends._ibm._ibm_http_client import show_devices def get_engine_list(token=None,device=None): - rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) #access to the hardware properties via show_devices #can be extended to take into account gate fidelities, new available gate, etc.. devices=show_devices(token) @@ -58,7 +56,13 @@ def get_engine_list(token=None,device=None): #the 32 qubit online simulator doesn't need a specific mapping for gates. #can also run wider gateset but this setup keep the restrictedgateset setup for #coherence - mapper = ManualMapper() + mapper = BasicMapperEngine() + #Note: Manual Mapper doesn't work, because its map is updated only if gates are applied + #if gates in the register are not used, then it will lead to state errors + res=dict() + for i in range(devices[device]['nq']): + res[i]=i + mapper.current_mapping = res ibm_setup=[mapper] elif device=='ibmq_16_melbourne': #Only 15 qubits available on this ibmqx2 unit(in particular qubit 7 on the grid), From 9a32b377322b4abe09c2f10f086eed97bcabb440 Mon Sep 17 00:00:00 2001 From: dbretaud Date: Thu, 16 Jan 2020 10:23:31 +0000 Subject: [PATCH 29/43] Revert "Merge branch 'develop' into ibm_V2" This reverts commit cd0452a5f56d8d7fc95dc17f6dc5d4970f3ad130, reversing changes made to 03daf7915ce663f8dc79975ba87dabb4534272e6. --- docs/projectq.setups.decompositions.rst | 20 +-- docs/projectq.setups.rst | 7 - projectq/cengines/_optimize.py | 16 +- projectq/cengines/_optimize_test.py | 18 -- projectq/cengines/_replacer/_replacer.py | 1 + projectq/ops/__init__.py | 1 - projectq/ops/_basics.py | 38 +---- projectq/ops/_basics_test.py | 21 +-- projectq/ops/_command.py | 14 +- projectq/ops/_command_test.py | 32 +--- projectq/ops/_metagates.py | 16 -- projectq/ops/_metagates_test.py | 10 -- projectq/setups/decompositions/__init__.py | 6 - projectq/setups/decompositions/cnot2rxx.py | 58 ------- .../setups/decompositions/cnot2rxx_test.py | 139 --------------- projectq/setups/decompositions/h2rx.py | 56 ------ projectq/setups/decompositions/h2rx_test.py | 121 ------------- projectq/setups/decompositions/rz2rx.py | 68 -------- projectq/setups/decompositions/rz2rx_test.py | 117 ------------- projectq/setups/restrictedgateset.py | 13 +- projectq/setups/trapped_ion_decomposer.py | 160 ------------------ .../setups/trapped_ion_decomposer_test.py | 62 ------- 22 files changed, 21 insertions(+), 973 deletions(-) mode change 100644 => 100755 projectq/ops/_metagates.py delete mode 100644 projectq/setups/decompositions/cnot2rxx.py delete mode 100644 projectq/setups/decompositions/cnot2rxx_test.py delete mode 100644 projectq/setups/decompositions/h2rx.py delete mode 100644 projectq/setups/decompositions/h2rx_test.py delete mode 100644 projectq/setups/decompositions/rz2rx.py delete mode 100644 projectq/setups/decompositions/rz2rx_test.py delete mode 100644 projectq/setups/trapped_ion_decomposer.py delete mode 100644 projectq/setups/trapped_ion_decomposer_test.py diff --git a/docs/projectq.setups.decompositions.rst b/docs/projectq.setups.decompositions.rst index 6206c4a95..26cd392fc 100755 --- a/docs/projectq.setups.decompositions.rst +++ b/docs/projectq.setups.decompositions.rst @@ -10,19 +10,16 @@ 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 @@ -65,13 +62,6 @@ 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 ------------------------------------------------------ @@ -100,8 +90,7 @@ projectq.setups.decompositions.globalphase module :members: :undoc-members: - -projectq.setups.decompositions.h2rx module +projectq.setups.decompositions.ph2r module ------------------------------------------ .. automodule:: projectq.setups.decompositions.ph2r @@ -143,13 +132,6 @@ 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 98e7e611a..058469f07 100755 --- a/docs/projectq.setups.rst +++ b/docs/projectq.setups.rst @@ -86,13 +86,6 @@ 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 0c9765288..2e72540b9 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 remove identity gates using the is_identity function, then merge or even cancel successive gates using the get_merged and + Try to 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,20 +130,6 @@ 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 ce8f937e5..e0196f83b 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -127,21 +127,3 @@ 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(2*math.pi) | qb0 - Ry(2*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 85fa303dc..29c883123 100755 --- a/projectq/cengines/_replacer/_replacer.py +++ b/projectq/cengines/_replacer/_replacer.py @@ -175,6 +175,7 @@ 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 dd73cc2d5..cac384d9e 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -25,7 +25,6 @@ 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 eb10ea74b..4bca84429 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -40,8 +40,6 @@ from ._command import Command, apply_command -import unicodedata - ANGLE_PRECISION = 12 ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION RTOL = 1e-10 @@ -226,20 +224,9 @@ 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): """ @@ -353,23 +340,10 @@ def __str__(self): Returns the class name and the angle as .. code-block:: python - - [CLASSNAME]([ANGLE]) - """ - return self.to_string() - - def to_string(self,symbols=False): - """ - Return the string representation of a BasicRotationGate. - Args: - symbols: uses the pi character and round the angle for a more user friendly display if True, full angle written in radian Otherwise + [CLASSNAME]([ANGLE]) """ - 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 + return str(self.__class__.__name__) + "(" + str(self.angle) + ")" def tex_str(self): """ @@ -381,7 +355,7 @@ def tex_str(self): [CLASSNAME]$_[ANGLE]$ """ - return str(self.__class__.__name__) + "$_{" + str(round(self.angle/math.pi,3)) + "\pi}$" + return str(self.__class__.__name__) + "$_{" + str(self.angle) + "}$" def get_inverse(self): """ @@ -427,12 +401,6 @@ 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): """ diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index a254b5b97..80cc80183 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -163,16 +163,15 @@ def test_basic_rotation_gate_init(input_angle, modulo_angle): def test_basic_rotation_gate_str(): - basic_rotation_gate = _basics.BasicRotationGate(math.pi) - assert str(basic_rotation_gate) == "BasicRotationGate(3.14159265359)" - assert basic_rotation_gate.to_string(symbols=True) == "BasicRotationGate(0.5π)" - assert basic_rotation_gate.to_string(symbols=False) == "BasicRotationGate(3.14159265359)" + basic_rotation_gate = _basics.BasicRotationGate(0.5) + assert str(basic_rotation_gate) == "BasicRotationGate(0.5)" + def test_basic_rotation_tex_str(): - basic_rotation_gate = _basics.BasicRotationGate(0.5*math.pi) - assert basic_rotation_gate.tex_str() == "BasicRotationGate$_{0.5\pi}$" + 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\pi}$" + assert basic_rotation_gate.tex_str() == "BasicRotationGate$_{0.0}$" @pytest.mark.parametrize("input_angle, inverse_angle", @@ -194,14 +193,6 @@ def test_basic_rotation_gate_get_merged(): merged_gate = basic_rotation_gate1.get_merged(basic_rotation_gate2) assert merged_gate == basic_rotation_gate3 -def test_basic_rotation_gate_is_identity(): - basic_gate = _basics.BasicGate() - basic_rotation_gate1 = _basics.BasicRotationGate(0.) - basic_rotation_gate2 = _basics.BasicRotationGate(1.0*math.pi) - basic_rotation_gate3 = _basics.BasicRotationGate(2.*math.pi) - assert basic_rotation_gate1.is_identity() - assert not basic_rotation_gate2.is_identity() - assert basic_rotation_gate3.is_identity() def test_basic_rotation_gate_comparison_and_hash(): basic_rotation_gate1 = _basics.BasicRotationGate(0.5) diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 59dff1a4e..5186502fa 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -149,15 +149,6 @@ def get_inverse(self): 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 @@ -306,9 +297,6 @@ 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. """ @@ -326,4 +314,4 @@ def to_string(self,symbols=False): qstring += ", " qstring = qstring[:-2] + " )" cstring = "C" * len(ctrlqubits) - return cstring + self.gate.to_string(symbols) + " | " + qstring + return cstring + str(self.gate) + " | " + qstring diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index 0f6963ae3..ae1407836 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -132,18 +132,6 @@ def test_command_get_merged(main_engine): with pytest.raises(NotMergeable): 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)]) @@ -244,21 +232,9 @@ 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*math.pi), (qubit,)) - cmd.tags = ["TestTag"] - cmd.add_control_qubits(ctrl_qubit) - assert str(cmd) == "CRx(1.570796326795‬) | ( Qureg[1], Qureg[0] )" - cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) - assert str(cmd2) == "Rx(1.570796326795‬) | 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 = _command.Command(main_engine, Rx(0.5), (qubit,)) cmd.tags = ["TestTag"] cmd.add_control_qubits(ctrl_qubit) - assert cmd.to_string(symbols=True) == "CRx(0.5π) | ( Qureg[1], Qureg[0] )" - assert cmd.to_string(symbols=False) == "CRx(1.570796326795‬) | ( Qureg[1], Qureg[0] )" - cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) - assert cmd2.to_string(symbols=True) == "Rx(0.5π) | Qureg[0]" - assert cmd2.to_string(symbols=False) == "Rx(1.570796326795‬) | Qureg[0]" \ No newline at end of file + 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]" diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py old mode 100644 new mode 100755 index cca5e7412..b2e7959fe --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -132,22 +132,6 @@ 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 8632a99e5..c42393b06 100755 --- a/projectq/ops/_metagates_test.py +++ b/projectq/ops/_metagates_test.py @@ -122,16 +122,6 @@ 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 d85b068e4..883f04581 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -17,18 +17,15 @@ carb1qubit2cnotrzandry, crz2cxandrz, cnot2cz, - cnot2rxx, cnu2toffoliandcu, entangle, globalphase, - h2rx, ph2r, qubitop2onequbit, qft2crandhadamard, r2rzandph, rx2rz, ry2rz, - rz2rx, sqrtswap2cnot, stateprep2cnot, swap2cnot, @@ -45,18 +42,15 @@ carb1qubit2cnotrzandry, crz2cxandrz, cnot2cz, - cnot2rxx, 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 deleted file mode 100644 index 30c888584..000000000 --- a/projectq/setups/decompositions/cnot2rxx.py +++ /dev/null @@ -1,58 +0,0 @@ -# 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 Compute, get_control_count, Uncompute -from projectq.ops import Rxx,Ry,Rx,Rz,X,Y,Z -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] - 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] - 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 deleted file mode 100644 index f37f7d587..000000000 --- a/projectq/setups/decompositions/cnot2rxx_test.py +++ /dev/null @@ -1,139 +0,0 @@ -# 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 - -import projectq -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 projectq.setups.decompositions 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 """ - g = cmd.gate - 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) == 9 - - # Create empty vectors for the wave vectors for the correct - # and test qubits - correct_vector = np.zeros((4,1),dtype=np.complex_) - test_vector = np.zeros((4,1),dtype=np.complex_) - i=0 - for fstate in range(4): - binary_state = format(fstate, '02b') - test = test_sim.get_amplitude(binary_state, - test_qb + test_ctrl_qb) - correct = correct_sim.get_amplitude(binary_state, correct_qb + - correct_ctrl_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) - - 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) - - \ No newline at end of file diff --git a/projectq/setups/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py deleted file mode 100644 index 62f372a62..000000000 --- a/projectq/setups/decompositions/h2rx.py +++ /dev/null @@ -1,56 +0,0 @@ -# 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 Compute, Control, get_control_count, Uncompute -from projectq.ops import Rx, Ry, Rz, 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 - 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 - 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 deleted file mode 100644 index caa266c0b..000000000 --- a/projectq/setups/decompositions/h2rx_test.py +++ /dev/null @@ -1,121 +0,0 @@ -# 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 math - -import numpy as np - -import pytest - -from projectq import MainEngine -from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - DummyEngine, InstructionFilter, MainEngine) -from projectq.meta import Control -from projectq.ops import Measure, Ph, H, HGate - -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 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(h_decomp_gates), - test_dummy_eng]) - - correct_qb = correct_eng.allocate_qubit() - H | correct_qb - correct_eng.flush() - - test_qb = test_eng.allocate_qubit() - H | test_qb - test_eng.flush() - - assert correct_dummy_eng.received_commands[1].gate == H - assert test_dummy_eng.received_commands[1].gate != H - - # 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 \ No newline at end of file diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py deleted file mode 100644 index 6b4a55ef7..000000000 --- a/projectq/setups/decompositions/rz2rx.py +++ /dev/null @@ -1,68 +0,0 @@ -# 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, H - - -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 deleted file mode 100644 index ca4ef68da..000000000 --- a/projectq/setups/decompositions/rz2rx_test.py +++ /dev/null @@ -1,117 +0,0 @@ -# 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, MainEngine) -from projectq.meta import Control -from projectq.ops import Measure, Ph, 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 8575ccedd..7b3540cf0 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -59,13 +59,10 @@ def one_and_two_qubit_gates(eng, cmd): else: 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=(), - compiler_chooser=default_chooser): + other_gates=()): """ Returns an engine list to compile to a restricted gate set. @@ -104,8 +101,6 @@ def get_engine_list(one_qubit_gates="any", 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 @@ -201,15 +196,15 @@ def low_level_gates(eng, cmd): return True return False - return [AutoReplacer(rule_set,compiler_chooser), + return [AutoReplacer(rule_set), TagRemover(), InstructionFilter(high_level_gates), LocalOptimizer(5), - AutoReplacer(rule_set,compiler_chooser), + AutoReplacer(rule_set), TagRemover(), InstructionFilter(one_and_two_qubit_gates), LocalOptimizer(5), - AutoReplacer(rule_set,compiler_chooser), + AutoReplacer(rule_set), TagRemover(), InstructionFilter(low_level_gates), LocalOptimizer(5), diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py deleted file mode 100644 index 0f1a3241e..000000000 --- a/projectq/setups/trapped_ion_decomposer.py +++ /dev/null @@ -1,160 +0,0 @@ -# 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. -""" - -import inspect - -import projectq -import projectq.libs.math -from projectq.setups import restrictedgateset -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - InstructionFilter, LocalOptimizer, - TagRemover) -from projectq.ops import (Rxx,Rx,Ry) - -# ------------------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) -# If the value is 1 then the last gate applied (during a decomposition!) was Ry(math.pi/2) -# If the value is 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 el in decomposition_list: - try: - decomposition=el.decompose.__name__.split('_') - name=decomposition[2] - decomp_rule[decomposition[3]]=el - # '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 - if name=='cnot2rxx': - idx = [qb.id for qb in cmd.control_qubits] # index of the qubit - assert len(idx)==1 # this should be a 2 qubit gate, therefore with 1 control qubit - idx=idx[0] - print(idx) - if idx in prev_Ry_sign: - if prev_Ry_sign[idx]<=0: - # If the previous qubit had Ry(-pi/2) - # choose the decomposition that starts - # with Ry(pi/2) - prev_Ry_sign[idx]=-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'] - else: - # Previous qubit had Ry(pi/2) - # choose decomposition that starts - # with Ry(-pi/2) and ends with R(pi/2) - prev_Ry_sign[idx]=1 - return decomp_rule['P'] - else: - # In this case where we have no previous Ry sign - # we choose decomp_rule['M'] - prev_Ry_sign[idx]=-1 - return decomp_rule['M'] - elif name=='h2rx': - # Block operates similar to 'cnot2rxx' case - idx = [qb.id for qb in cmd.qubits[0]] - assert len(idx)==1 # this should be a single qubit gate - idx=idx[0] - if idx not in prev_Ry_sign: - prev_Ry_sign[idx]=+1 - return decomp_rule['M'] - elif prev_Ry_sign[idx]==0: - prev_Ry_sign[idx]=+1 - return decomp_rule['M'] - else: - prev_Ry_sign[idx]=00 - return decomp_rule['N'] - elif name=='rz2rx': - # Block operates similar to 'cnot2rxx' case - idx = [qb.id for qb in cmd.qubits[0]] - assert len(idx)==1 # this should be a single qubit gate - idx=idx[0] - if idx not in prev_Ry_sign: - prev_Ry_sign[idx]=-1 - return decomp_rule['M'] - elif prev_Ry_sign[idx]<=0: - prev_Ry_sign[idx]=-1 - return decomp_rule['M'] - else: - prev_Ry_sign[idx]=1 - return decomp_rule['P'] - else: # 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 deleted file mode 100644 index 5a59458b4..000000000 --- a/projectq/setups/trapped_ion_decomposer_test.py +++ /dev/null @@ -1,62 +0,0 @@ -# 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 math -import projectq -from projectq import MainEngine -from projectq.ops import Rx, Ry, Rz, H, CNOT, Measure, All, X, Rxx -from projectq.meta import Compute, Control, Uncompute -from projectq.cengines import DecompositionRule -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, - InstructionFilter) -import pytest -import projectq.setups.restrictedgateset as restrictedgateset -from projectq.setups.trapped_ion_decomposer import 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 and Measure commands, this would -# result in 12 commands. -# Using the chooser_Rx_reducer you get 9 commands, since you -# now have 4 single qubit gates and 1 two qubit gate. - -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 and Measure commands, this - results in 12 commands. - Using the chooser_Rx_reducer you get 9 commands, the - decomposotion having resulted in 4 single qubit gates - and 1 two qubit gate. - """ - engine_list = restrictedgateset.get_engine_list( - one_qubit_gates=(Rx, Ry), - two_qubit_gates=(Rxx,), compiler_chooser=chooser_Ry_reducer) - 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 - assert len(backend.received_commands) < 12 - From 17af2c89ee48ecb1ad53623be3866ae3bb8be726 Mon Sep 17 00:00:00 2001 From: dbretaud Date: Thu, 16 Jan 2020 14:01:14 +0000 Subject: [PATCH 30/43] minor fixes --- projectq/backends/_ibm/_ibm.py | 1 - projectq/backends/_ibm/_ibm_http_client.py | 6 ++- .../backends/_ibm/_ibm_http_client_test.py | 40 +++++++++---------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index 4adbdce43..84e330688 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -281,7 +281,6 @@ def _run(self): if self._retrieve_execution is None: res = send(info, device=self.device, token=self._token, - shots=self._num_runs, num_retries=self._num_retries, interval=self._interval, verbose=self._verbose) diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 0d794fd27..e9f3f09ef 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -231,7 +231,7 @@ def retrieve(device, token, jobid, num_retries=3000, def send(info, device='ibmq_qasm_simulator',token=None, - shots=1, num_retries=3000, interval=1, verbose=False): + shots=None, num_retries=3000, interval=1, verbose=False): """ Sends QASM through the IBM API and runs the quantum circuit. @@ -250,7 +250,9 @@ def send(info, device='ibmq_qasm_simulator',token=None, """ try: ibmq_session=IBMQ() - + #shots argument deprecated, as already + if shots is not None: + info['shots']=shots if verbose: print("- Authenticating...") print('user API token: '+token) diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index a5393b2e5..d14e88a9f 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -31,7 +31,8 @@ def no_requests(monkeypatch): _api_url = 'https://api.quantum-computing.ibm.com/api/' def test_send_real_device_online_verbose(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}]} + qasms = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, + 'json': 'instructions','maxCredits': 10,'nq': 1} json_qasm = json.dumps(qasms) name = 'projectq_test' access_token = "access" @@ -42,7 +43,6 @@ def test_send_real_device_online_verbose(monkeypatch): json_data = ''.join(['{', json_body, '}']) shots = 1 device = "ibmqx4" - json_data_run = ''.join(['{"qasm":', json_qasm, '}']) execution_id = 3 result_ready = [False] result = "my_result" @@ -80,13 +80,12 @@ def raise_for_status(self): result_ready[0] and request_num[0] == 3): result_ready[0] = True request_num[0] += 1 - return MockResponse({"status": {"id": "RUNNING"}}, 200) + return MockResponse({"status": "RUNNING"}, 200) elif (args[0] == urljoin(_api_url, "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id)) and kwargs["params"]["access_token"] == access_token and result_ready[0] and request_num[0] == 4): - print("state ok") - return MockResponse({"qasms": [{"result": result}]}, 200) + return MockResponse({"results": [result],"status": "COMPLETED"}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -108,18 +107,17 @@ def raise_for_status(self): # Authentication if (args[0] == urljoin(_api_url, "users/loginWithToken") and - kwargs["json"]["apiToken"] == token + kwargs["json"]["apiToken"] == token and request_num[0] == 1): request_num[0] += 1 return MockPostResponse({"userId": user_id, "id": access_token}) # Run code elif (args[0] == urljoin(_api_url, "Jobs") and - kwargs["data"] == json_qasm and + kwargs["data"] == None and kwargs["params"]["access_token"] == access_token and - kwargs["params"]["deviceRunType"] == device and - kwargs["params"]["fromCache"] == "false" and - kwargs["params"]["shots"] == shots and - kwargs["headers"]["Content-Type"] == "application/json" and + kwargs["backend"]["name"] == device and + kwargs["json"]["qObject"]['config']['shots'] == shots and + kwargs["headers"]["X-Qx-Client-Application"] == "ibmqprovider/0.4.4" and request_num[0] == 2): request_num[0] += 1 return MockPostResponse({"id": execution_id}) @@ -140,7 +138,6 @@ def user_password_input(prompt): device="ibmqx4", token=None, shots=shots, verbose=True) - print(res) assert res == result @@ -289,7 +286,8 @@ def user_password_input(prompt): def test_timeout_exception(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}]} + qasms = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, + 'json': 'instructions','maxCredits': 10,'nq': 1} json_qasm = json.dumps(qasms) tries = [0] @@ -350,7 +348,8 @@ def raise_for_status(self): def test_retrieve_and_device_offline_exception(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}]} + qasms = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, + 'json': 'instructions','maxCredits': 10,'nq': 1} json_qasm = json.dumps(qasms) request_num = [0] @@ -375,7 +374,7 @@ def raise_for_status(self): job_url = 'Jobs/{}'.format("123e") if args[0] == urljoin(_api_url, job_url): request_num[0] += 1 - return MockResponse({"noqasms": "not done"}, 200) + return MockResponse({"status": "RUNNING"}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -403,12 +402,13 @@ def raise_for_status(self): _ibm_http_client.time.sleep = lambda x: x with pytest.raises(_ibm_http_client.DeviceOfflineError): _ibm_http_client.retrieve(device="ibmqx4", - user="test", password="test", + token=token, jobid="123e") def test_retrieve(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}]} + qasms = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, + 'json': 'instructions','maxCredits': 10,'nq': 1} json_qasm = json.dumps(qasms) request_num = [0] @@ -431,10 +431,10 @@ def raise_for_status(self): job_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/{}'.format("123e") if args[0] == urljoin(_api_url, job_url) and request_num[0] < 1: request_num[0] += 1 - return MockResponse({"status": {"id": "RUNNING"}}, 200) + return MockResponse({"status": "RUNNING"}, 200) elif args[0] == urljoin(_api_url, job_url): - return MockResponse({"qObjectResult": [{'qasm': 'qasm', - 'result': 'correct'}],"status": {"id": "COMPLETED"}}, 200) + return MockResponse({"qObjectResult": {'qasm': 'qasm', + 'results': ['correct']},"status": "COMPLETED"}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: From 483cb24b005aee98415982be6de1c84382a812f9 Mon Sep 17 00:00:00 2001 From: dbretaud Date: Thu, 16 Jan 2020 17:41:20 +0000 Subject: [PATCH 31/43] bug fix in client test file --- projectq/backends/_ibm/_ibm_http_client_test.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index d14e88a9f..1cf01c274 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -310,7 +310,7 @@ def raise_for_status(self): job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format("123e") if args[0] == urljoin(_api_url, job_url): tries[0] += 1 - return MockResponse({"status": {"id": "RUNNING"}}, 200) + return MockResponse({"status": "RUNNING"}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -342,15 +342,12 @@ def raise_for_status(self): _ibm_http_client.send(json_qasm, device="ibmqx4", token="test", - shots=1,num_retries=10 verbose=False) + shots=1,num_retries=10, verbose=False) assert "123e" in str(excinfo.value) # check that job id is in exception assert tries[0] > 0 def test_retrieve_and_device_offline_exception(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, - 'json': 'instructions','maxCredits': 10,'nq': 1} - json_qasm = json.dumps(qasms) request_num = [0] def mocked_requests_get(*args, **kwargs): @@ -407,9 +404,6 @@ def raise_for_status(self): def test_retrieve(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, - 'json': 'instructions','maxCredits': 10,'nq': 1} - json_qasm = json.dumps(qasms) request_num = [0] def mocked_requests_get(*args, **kwargs): From 43939fc27f529a54b9cd37674c9d1fbf03446e19 Mon Sep 17 00:00:00 2001 From: dbretaud Date: Fri, 17 Jan 2020 01:55:28 +0000 Subject: [PATCH 32/43] fixing bug and python2.7 compatibility for testing files --- projectq/backends/_ibm/_ibm.py | 2 +- projectq/backends/_ibm/_ibm_http_client.py | 2 +- projectq/backends/_ibm/_ibm_http_client_test.py | 2 +- projectq/backends/_ibm/_ibm_test.py | 1 + projectq/cengines/_ibm5qubitmapper.py | 5 +++-- projectq/cengines/_ibm5qubitmapper_test.py | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index 84e330688..8177dc301 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -98,7 +98,7 @@ def is_available(self, cmd): cmd (Command): Command for which to check availability """ g = cmd.gate - if g == NOT and get_control_count(cmd) <= 1: + if g == NOT and get_control_count(cmd) == 1: return True if get_control_count(cmd) == 0: if g == H: diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index e9f3f09ef..0bc92d33f 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -33,7 +33,7 @@ class IBMQ(Session): def __init__(self): - super().__init__() + super(Session,self).__init__()#Python 2 compatibility self.backends=dict() self.timeout=5.0 diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index 1cf01c274..e55a69ee5 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -399,7 +399,7 @@ def raise_for_status(self): _ibm_http_client.time.sleep = lambda x: x with pytest.raises(_ibm_http_client.DeviceOfflineError): _ibm_http_client.retrieve(device="ibmqx4", - token=token, + token="test", jobid="123e") diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index 90fb094db..d9a33921c 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -24,6 +24,7 @@ LocalOptimizer, AutoReplacer, IBM5QubitMapper, + BasicMapperEngine, SwapAndCNOTFlipper, DummyEngine, DecompositionRuleSet) diff --git a/projectq/cengines/_ibm5qubitmapper.py b/projectq/cengines/_ibm5qubitmapper.py index 348337fb2..d60525a96 100755 --- a/projectq/cengines/_ibm5qubitmapper.py +++ b/projectq/cengines/_ibm5qubitmapper.py @@ -50,8 +50,9 @@ def __init__(self, connections=None): self.current_mapping = dict() self._reset() if connections is None: - self.connections=set([(0, 1), (1, 0), (1, 2), (1, 3), - (2, 1), (3, 1), (3, 4), (4, 3)]) + #general connectivity easier for testing functions + self.connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) else: self.connections=connections diff --git a/projectq/cengines/_ibm5qubitmapper_test.py b/projectq/cengines/_ibm5qubitmapper_test.py index 5c4c4c4da..2256a27ae 100755 --- a/projectq/cengines/_ibm5qubitmapper_test.py +++ b/projectq/cengines/_ibm5qubitmapper_test.py @@ -120,7 +120,7 @@ def test_ibm5qubitmapper_optimizeifpossible(): backend = DummyEngine(save_commands=True) connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper(), + engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity), SwapAndCNOTFlipper(connectivity)]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() From 6173caccda657a73485df32bade4540bdddbee89 Mon Sep 17 00:00:00 2001 From: dbretaud Date: Fri, 17 Jan 2020 20:53:20 +0000 Subject: [PATCH 33/43] fix errors --- projectq/backends/_ibm/_ibm_http_client.py | 2 +- .../backends/_ibm/_ibm_http_client_test.py | 40 +++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 0bc92d33f..91bd61de0 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -96,7 +96,7 @@ def _authenticate(self,token=None): Returns: """ if token is None: - token = getpass.getpass(prompt='IBM Q token > ') + token = getpass.getpass(prompt="IBM QE token > ") self.headers.update({'X-Qx-Client-Application': CLIENT_APPLICATION}) args={'data': None, 'json': {'apiToken': token}, 'timeout': (self.timeout, None)} r = super().request('POST', _auth_api_url, **args) diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index e55a69ee5..5cc2b8400 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -29,12 +29,14 @@ def no_requests(monkeypatch): _api_url = 'https://api.quantum-computing.ibm.com/api/' +_auth_api_url = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' def test_send_real_device_online_verbose(monkeypatch): qasms = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, 'json': 'instructions','maxCredits': 10,'nq': 1} json_qasm = json.dumps(qasms) name = 'projectq_test' + token = '12345' access_token = "access" user_id = 2016 code_id = 11 @@ -72,7 +74,9 @@ def raise_for_status(self): if (args[0] == urljoin(_api_url_status, status_url) and (request_num[0] == 0 or request_num[0] == 3)): request_num[0] += 1 - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) # Getting result elif (args[0] == urljoin(_api_url, "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id)) and @@ -106,7 +110,7 @@ def raise_for_status(self): pass # Authentication - if (args[0] == urljoin(_api_url, "users/loginWithToken") and + if (args[0] == _auth_api_url) and kwargs["json"]["apiToken"] == token and request_num[0] == 1): request_num[0] += 1 @@ -124,8 +128,6 @@ def raise_for_status(self): monkeypatch.setattr("requests.get", mocked_requests_get) monkeypatch.setattr("requests.post", mocked_requests_post) - # Patch login data - token = 12345 def user_password_input(prompt): if prompt == "IBM QE token > ": @@ -154,15 +156,16 @@ def json(self): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[0] == urljoin(_api_url, status_url): - return MockResponse({"state": False}, 200) + return MockResponse({}, 200) monkeypatch.setattr("requests.get", mocked_requests_get) shots = 1 + token='12345' json_qasm = "my_json_qasm" name = 'projectq_test' with pytest.raises(_ibm_http_client.DeviceOfflineError): _ibm_http_client.send(json_qasm, device="ibmqx4", - token=None, + token=token, shots=shots, verbose=True) @@ -179,7 +182,9 @@ def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[0] == urljoin(_api_url_status, status_url): - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) def mocked_requests_post(*args, **kwargs): # Test that this error gets caught @@ -219,7 +224,9 @@ def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[0] == urljoin(_api_url_status, status_url): - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) def mocked_requests_post(*args, **kwargs): # Test that this error gets caught @@ -258,7 +265,9 @@ def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[0] == urljoin(_api_url_status, status_url): - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) def mocked_requests_post(*args, **kwargs): # Test that this error gets caught @@ -306,7 +315,9 @@ def raise_for_status(self): # Accessing status of device. Return device info. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' if args[0] == urljoin(_api_url_status, status_url): - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format("123e") if args[0] == urljoin(_api_url, job_url): tries[0] += 1 @@ -329,8 +340,7 @@ def json(self): def raise_for_status(self): pass - login_url = 'users/loginWithToken' - if args[0] == urljoin(_api_url, login_url): + if args[0] == _auth_api_url: return MockPostResponse({"userId": "1", "id": "12"}) if args[0] == urljoin(_api_url, 'Jobs'): return MockPostResponse({"id": "123e"}) @@ -390,8 +400,7 @@ def json(self): def raise_for_status(self): pass - login_url = 'users/loginWithToken' - if args[0] == urljoin(_api_url, login_url): + if args[0] == _auth_api_url: return MockPostResponse({"userId": "1", "id": "12"}) monkeypatch.setattr("requests.get", mocked_requests_get) @@ -447,8 +456,7 @@ def json(self): def raise_for_status(self): pass - login_url = 'users/loginWithToken' - if args[0] == urljoin(_api_url, login_url): + if args[0] == _auth_api_url: return MockPostResponse({"userId": "1", "id": "12"}) monkeypatch.setattr("requests.get", mocked_requests_get) From 83be8e9cc33acf1084006b7a7fbc1062c06cf489 Mon Sep 17 00:00:00 2001 From: dbretaud Date: Sun, 19 Jan 2020 00:41:18 +0000 Subject: [PATCH 34/43] major fix on testing files --- projectq/backends/_ibm/_ibm.py | 1 - projectq/backends/_ibm/_ibm_http_client.py | 35 ++-- .../backends/_ibm/_ibm_http_client_test.py | 162 +++++++++++------- projectq/backends/_ibm/_ibm_test.py | 24 +-- projectq/cengines/_ibm5qubitmapper_test.py | 12 +- projectq/setups/ibm_test.py | 13 +- 6 files changed, 154 insertions(+), 93 deletions(-) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index 8177dc301..6ab2c1122 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -265,7 +265,6 @@ def _run(self): # return if no operations / measurements have been performed. if self.qasm == "": return - max_qubit_id = max(self._allocated_qubits) + 1 qasm = ("\ninclude \"qelib1.inc\";\nqreg q[{nq}];\ncreg c[{nq}];" + self.qasm).format(nq=max_qubit_id) diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 91bd61de0..ca2ccf97d 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -32,8 +32,8 @@ class IBMQ(Session): - def __init__(self): - super(Session,self).__init__()#Python 2 compatibility + def __init__(self, **kwargs): + super().__init__( **kwargs)#Python 2 compatibility self.backends=dict() self.timeout=5.0 @@ -50,9 +50,10 @@ def get_list_devices(self,verbose=False): """ list_device_url='Network/ibm-q/Groups/open/Projects/main/devices/v/1' argument={'allow_redirects': True, 'timeout': (self.timeout, None)} - r = super().request('GET', urljoin(_api_url, list_device_url), **argument) + r = super().get(urljoin(_api_url, list_device_url), **argument) r.raise_for_status() r_json=r.json() + print(r_json) self.backends=dict() for el in r_json: self.backends[el['backend_name']]={'nq':el['n_qubits'],'coupling_map':el['coupling_map'],'version':el['backend_version']} @@ -97,12 +98,14 @@ def _authenticate(self,token=None): """ if token is None: token = getpass.getpass(prompt="IBM QE token > ") + if len(token)==0: + raise Exception('Error with the IBM QE token') self.headers.update({'X-Qx-Client-Application': CLIENT_APPLICATION}) args={'data': None, 'json': {'apiToken': token}, 'timeout': (self.timeout, None)} - r = super().request('POST', _auth_api_url, **args) - + r = super().post(_auth_api_url, **args) r.raise_for_status() r_json=r.json() + print(r_json) self.params.update({'access_token': r_json['id']}) return @@ -115,7 +118,6 @@ def _run(self,info, device): version=self.backends[device]['version'] instructions=info['json'] maxcredit=info['maxCredits'] - c_label=[] q_label=[] for i in range(nq): @@ -138,11 +140,12 @@ def _run(self,info, device): 'header': {'backend_version': version, 'backend_name': device}, 'qobj_id': 'e72443f5-7752-4e32-9ac8-156f1f3fee18'}, - 'backend': {'name': device}, 'shots': shots}, + 'backend': {'name': device}, 'shots': shots}, 'timeout': (self.timeout, None)} - r = super().request('POST', urljoin(_api_url, post_job_url), **argument) + r = super().post(urljoin(_api_url, post_job_url), **argument) r.raise_for_status() r_json=r.json() + print(r.json()) execution_id = r_json["id"] return execution_id @@ -162,13 +165,17 @@ def _handle_sigint_during_get_result(*_): try: signal.signal(signal.SIGINT, _handle_sigint_during_get_result) - + print('is online') + print(self.is_online(device)) for retries in range(num_retries): argument={'allow_redirects': True, 'timeout': (self.timeout, None)} - r = super().request('GET', urljoin(_api_url, job_status_url), **argument) + r = super().get(urljoin(_api_url, job_status_url), **argument) r.raise_for_status() r_json = r.json() + print(r_json) + print(retries) + print(retries % 60) if r_json['status']=='COMPLETED': return r_json['qObjectResult']['results'][0] elif r_json['status']!='RUNNING': @@ -176,7 +183,7 @@ def _handle_sigint_during_get_result(*_): .format(r_json['status'])) time.sleep(interval) if self.is_online(device) and retries % 60 == 0: - self.get_list_devices() + print(self.get_list_devices()) if not self.is_online(device): raise DeviceOfflineError("Device went offline. The ID of " "your submitted job is {}." @@ -224,8 +231,9 @@ def retrieve(device, token, jobid, num_retries=3000, (dict) result form the IBMQ server """ ibmq_session=IBMQ() - imbq_session._authenticate(token) - res = imbq_session._get_result(device, jobid, num_retries=num_retries, + ibmq_session._authenticate(token) + ibmq_session.get_list_devices(verbose) + res = ibmq_session._get_result(device, jobid, num_retries=num_retries, interval=interval, verbose=verbose) return res @@ -255,6 +263,7 @@ def send(info, device='ibmq_qasm_simulator',token=None, info['shots']=shots if verbose: print("- Authenticating...") + if token is not None: print('user API token: '+token) ibmq_session._authenticate(token) diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index 5cc2b8400..ca4885b82 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -32,20 +32,19 @@ def no_requests(monkeypatch): _auth_api_url = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' def test_send_real_device_online_verbose(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, + json_qasm = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, 'json': 'instructions','maxCredits': 10,'nq': 1} - json_qasm = json.dumps(qasms) name = 'projectq_test' token = '12345' access_token = "access" user_id = 2016 code_id = 11 name_item = '"name":"{name}", "jsonQASM":'.format(name=name) - json_body = ''.join([name_item, json_qasm]) + json_body = ''.join([name_item, json.dumps(json_qasm)]) json_data = ''.join(['{', json_body, '}']) shots = 1 device = "ibmqx4" - execution_id = 3 + execution_id = '3' result_ready = [False] result = "my_result" request_num = [0] # To assert correct order of calls @@ -71,25 +70,23 @@ def raise_for_status(self): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if (args[0] == urljoin(_api_url_status, status_url) and - (request_num[0] == 0 or request_num[0] == 3)): + if (args[1] == urljoin(_api_url, status_url) and + (request_num[0] == 1 or request_num[0] == 4)): request_num[0] += 1 connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) # Getting result - elif (args[0] == urljoin(_api_url, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id)) and - kwargs["params"]["access_token"] == access_token and not + elif (args[1] == urljoin(_api_url, + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id)) and not result_ready[0] and request_num[0] == 3): result_ready[0] = True request_num[0] += 1 return MockResponse({"status": "RUNNING"}, 200) - elif (args[0] == urljoin(_api_url, + elif (args[1] == urljoin(_api_url, "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id)) and - kwargs["params"]["access_token"] == access_token and - result_ready[0] and request_num[0] == 4): - return MockResponse({"results": [result],"status": "COMPLETED"}, 200) + result_ready[0] and request_num[0] == 5): + return MockResponse({'qObjectResult':{"results": [result]},"status": "COMPLETED"}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -108,26 +105,24 @@ def json(self): def raise_for_status(self): pass - + jobs_url='Network/ibm-q/Groups/open/Projects/main/Jobs' # Authentication - if (args[0] == _auth_api_url) and + if (args[1] == _auth_api_url and kwargs["json"]["apiToken"] == token and - request_num[0] == 1): + request_num[0] == 0): request_num[0] += 1 return MockPostResponse({"userId": user_id, "id": access_token}) # Run code - elif (args[0] == urljoin(_api_url, "Jobs") and + elif (args[1] == urljoin(_api_url, jobs_url) and kwargs["data"] == None and - kwargs["params"]["access_token"] == access_token and - kwargs["backend"]["name"] == device and + kwargs["json"]["backend"]["name"] == device and kwargs["json"]["qObject"]['config']['shots'] == shots and - kwargs["headers"]["X-Qx-Client-Application"] == "ibmqprovider/0.4.4" and request_num[0] == 2): request_num[0] += 1 return MockPostResponse({"id": execution_id}) - - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) def user_password_input(prompt): if prompt == "IBM QE token > ": @@ -144,6 +139,9 @@ def user_password_input(prompt): def test_send_real_device_offline(monkeypatch): + token = '12345' + access_token = "access" + user_id = 2016 def mocked_requests_get(*args, **kwargs): class MockResponse: def __init__(self, json_data, status_code): @@ -153,14 +151,42 @@ def __init__(self, json_data, status_code): def json(self): return self.json_data - # Accessing status of device. Return online. + def raise_for_status(self): + pass + + # Accessing status of device. Return offline. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[0] == urljoin(_api_url, status_url): + if args[1] == urljoin(_api_url, status_url): return MockResponse({}, 200) - monkeypatch.setattr("requests.get", mocked_requests_get) + + def mocked_requests_post(*args, **kwargs): + class MockRequest: + def __init__(self, body="", url=""): + self.body = body + self.url = url + + class MockPostResponse: + def __init__(self, json_data, text=" "): + self.json_data = json_data + self.text = text + self.request = MockRequest() + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + # Authentication + if (args[1] == _auth_api_url and kwargs["json"]["apiToken"] == token): + return MockPostResponse({"userId": user_id, "id": access_token}) + + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) + shots = 1 token='12345' - json_qasm = "my_json_qasm" + json_qasm = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, + 'json': 'instructions','maxCredits': 10,'nq': 1} name = 'projectq_test' with pytest.raises(_ibm_http_client.DeviceOfflineError): _ibm_http_client.send(json_qasm, @@ -181,7 +207,7 @@ def json(self): def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[0] == urljoin(_api_url_status, status_url): + if args[1] == urljoin(_api_url_status, status_url): connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) @@ -190,10 +216,10 @@ def mocked_requests_post(*args, **kwargs): # Test that this error gets caught raise requests.exceptions.HTTPError - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) # Patch login data - token = 12345 + token = '12345' def user_password_input(prompt): if prompt == "IBM QE token > ": @@ -201,7 +227,8 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 - json_qasm = "my_json_qasm" + json_qasm = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, + 'json': 'instructions','maxCredits': 10,'nq': 1} name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", @@ -223,7 +250,7 @@ def json(self): def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[0] == urljoin(_api_url_status, status_url): + if args[1] == urljoin(_api_url_status, status_url): connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) @@ -232,10 +259,10 @@ def mocked_requests_post(*args, **kwargs): # Test that this error gets caught raise requests.exceptions.RequestException - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) # Patch login data - token = 12345 + token = '12345' def user_password_input(prompt): if prompt == "IBM QE token > ": @@ -243,7 +270,8 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 - json_qasm = "my_json_qasm" + json_qasm = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, + 'json': 'instructions','maxCredits': 10,'nq': 1} name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", @@ -264,7 +292,7 @@ def json(self): def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[0] == urljoin(_api_url_status, status_url): + if args[1] == urljoin(_api_url_status, status_url): connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) @@ -273,10 +301,10 @@ def mocked_requests_post(*args, **kwargs): # Test that this error gets caught raise KeyError - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) # Patch login data - token = 12345 + token = '12345' def user_password_input(prompt): if prompt == "IBM QE token > ": @@ -284,7 +312,8 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 - json_qasm = "my_json_qasm" + json_qasm = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, + 'json': 'instructions','maxCredits': 10,'nq': 1} name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", @@ -297,7 +326,7 @@ def user_password_input(prompt): def test_timeout_exception(monkeypatch): qasms = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, 'json': 'instructions','maxCredits': 10,'nq': 1} - json_qasm = json.dumps(qasms) + json_qasm = qasms tries = [0] def mocked_requests_get(*args, **kwargs): @@ -314,12 +343,12 @@ def raise_for_status(self): # Accessing status of device. Return device info. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[0] == urljoin(_api_url_status, status_url): + if args[1] == urljoin(_api_url, status_url): connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format("123e") - if args[0] == urljoin(_api_url, job_url): + if args[1] == urljoin(_api_url, job_url): tries[0] += 1 return MockResponse({"status": "RUNNING"}, 200) @@ -340,13 +369,15 @@ def json(self): def raise_for_status(self): pass - if args[0] == _auth_api_url: + jobs_url='Network/ibm-q/Groups/open/Projects/main/Jobs' + if args[1] == _auth_api_url: return MockPostResponse({"userId": "1", "id": "12"}) - if args[0] == urljoin(_api_url, 'Jobs'): + if args[1] == urljoin(_api_url, jobs_url): return MockPostResponse({"id": "123e"}) - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) + _ibm_http_client.time.sleep = lambda x: x with pytest.raises(Exception) as excinfo: _ibm_http_client.send(json_qasm, @@ -374,14 +405,14 @@ def raise_for_status(self): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[0] == urljoin(_api_url, status_url) and request_num[0] < 2: + if args[1] == urljoin(_api_url, status_url) and request_num[0] < 2: return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) - elif args[0] == urljoin(_api_url, status_url):#ibmqx4 gets disconnected, replaced by ibmqx5 + elif args[1] == urljoin(_api_url, status_url):#ibmqx4 gets disconnected, replaced by ibmqx5 return MockResponse([{'backend_name': 'ibmqx5', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) - job_url = 'Jobs/{}'.format("123e") - if args[0] == urljoin(_api_url, job_url): + job_url = job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format("123e") + if args[1] == urljoin(_api_url, job_url): request_num[0] += 1 - return MockResponse({"status": "RUNNING"}, 200) + return MockResponse({"status": "RUNNING",'iteration':request_num[0]}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -400,16 +431,18 @@ def json(self): def raise_for_status(self): pass - if args[0] == _auth_api_url: + if args[1] == _auth_api_url: return MockPostResponse({"userId": "1", "id": "12"}) - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) + _ibm_http_client.time.sleep = lambda x: x with pytest.raises(_ibm_http_client.DeviceOfflineError): _ibm_http_client.retrieve(device="ibmqx4", token="test", - jobid="123e") + jobid="123e", + num_retries=200) def test_retrieve(monkeypatch): @@ -429,13 +462,13 @@ def raise_for_status(self): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[0] == urljoin(_api_url, status_url): + if args[1] == urljoin(_api_url, status_url): return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) job_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/{}'.format("123e") - if args[0] == urljoin(_api_url, job_url) and request_num[0] < 1: + if args[1] == urljoin(_api_url, job_url) and request_num[0] < 1: request_num[0] += 1 return MockResponse({"status": "RUNNING"}, 200) - elif args[0] == urljoin(_api_url, job_url): + elif args[1] == urljoin(_api_url, job_url): return MockResponse({"qObjectResult": {'qasm': 'qasm', 'results': ['correct']},"status": "COMPLETED"}, 200) @@ -456,11 +489,12 @@ def json(self): def raise_for_status(self): pass - if args[0] == _auth_api_url: + if args[1] == _auth_api_url: return MockPostResponse({"userId": "1", "id": "12"}) - monkeypatch.setattr("requests.get", mocked_requests_get) - monkeypatch.setattr("requests.post", mocked_requests_post) + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) + _ibm_http_client.time.sleep = lambda x: x res = _ibm_http_client.retrieve(device="ibmqx4", token="test", diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index d9a33921c..a68c4737f 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -15,9 +15,10 @@ """Tests for projectq.backends._ibm._ibm.py.""" import pytest +import requests import json - -import projectq.setups.restrictedgateset +import math +from projectq.setups import restrictedgateset from projectq import MainEngine from projectq.backends._ibm import _ibm from projectq.cengines import (TagRemover, @@ -30,7 +31,7 @@ DecompositionRuleSet) from projectq.ops import (All, Allocate, Barrier, Command, Deallocate, Entangle, Measure, NOT, Rx, Ry, Rz, S, Sdag, T, Tdag, - X, Y, Z, H) + X, Y, Z, H, CNOT) # Insure that no HTTP request can be made in all tests in this module @@ -53,7 +54,7 @@ def test_ibm_backend_is_available(single_qubit_gate, is_available): @pytest.mark.parametrize("num_ctrl_qubits, is_available", [ - (0, True), (1, True), (2, False), (3, False)]) + (0, False), (1, True), (2, False), (3, False)]) def test_ibm_backend_is_available_control_not(num_ctrl_qubits, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() @@ -122,7 +123,7 @@ def mock_retrieve(*args, **kwargs): 'success': True, 'time_taken': 0.0045786460000000005} monkeypatch.setattr(_ibm, "retrieve", mock_retrieve) - backend = _ibm.IBMBackend(retrieve_execution="ab1s2") + backend = _ibm.IBMBackend(retrieve_execution="ab1s2",num_runs=1000) mapper = BasicMapperEngine() res=dict() for i in range(4): @@ -147,12 +148,14 @@ def mock_retrieve(*args, **kwargs): # run the circuit eng.flush() prob_dict = eng.backend.get_probabilities([qureg[0], qureg[2], qureg[1]]) + assert prob_dict['000'] == pytest.approx(0.504) assert prob_dict['111'] == pytest.approx(0.482) assert prob_dict['011'] == pytest.approx(0.006) def test_ibm_backend_functional_test(monkeypatch): - correct_info = {'qasms': [{'qasm': '\ninclude "qelib1.inc";\nqreg q[4];\ncreg c[4];\nu2(0,pi/2) q[1];' + correct_info = {'qasms': [{'qasm': '\ninclude "qelib1.inc";\nqreg q[4];\ncreg c[4];\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];'}], 'json': [{'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793]}, {'qubits': [1, 2], 'name': 'cx'}, {'qubits': [1, 3], 'name': 'cx'}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [11.780972450962]}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [10.995574287564]}, {'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966]}, {'qubits': [1], 'name': 'measure', 'memory': [1]}, {'qubits': [2], 'name': 'measure', 'memory': [2]}, {'qubits': [3], 'name': 'measure', 'memory': [3]}], 'nq': 4, 'shots': 1000, 'maxCredits': 10, 'backend': {'name': 'ibmq_qasm_simulator'}} + """{'qasms': [{'qasm': '\ninclude "qelib1.inc";\nqreg q[4];\ncreg c[4];\nu2(0,pi/2) q[1];' '\ncx q[1], q[2];\ncx q[1], q[3];\nu3(6.28318530718, 0, 0) q[1];' '\nu1(11.780972450962) q[1];\nu3(6.28318530718, 0, 0) q[1];' '\nu1(10.995574287564) q[1];\nu3(0.2, -pi/2, pi/2) q[1];' @@ -170,10 +173,10 @@ def test_ibm_backend_functional_test(monkeypatch): 'nq': 4, 'shots': 1000, 'maxCredits': 10, - 'backend': {'name': 'ibmq_qasm_simulator'}} + 'backend': {'name': 'ibmq_qasm_simulator'}}""" def mock_send(*args, **kwargs): - assert json.loads(args[0]) == json.loads(correct_info) + assert args[0] == correct_info return {'data': {'counts': {'0x0': 504, '0x2': 8, '0xc': 6, '0xe': 482}}, 'header': {'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]], 'creg_sizes': [['c', 4]], @@ -196,13 +199,13 @@ def mock_send(*args, **kwargs): 'time_taken': 0.0045786460000000005} monkeypatch.setattr(_ibm, "send", mock_send) - backend = _ibm.IBMBackend(verbose=True) + backend = _ibm.IBMBackend(verbose=True,num_runs=1000) # no circuit has been executed -> raises exception with pytest.raises(RuntimeError): backend.get_probabilities([]) mapper = BasicMapperEngine() res=dict() - for i in range(3): + for i in range(4): res[i]=i mapper.current_mapping = res ibm_setup=[mapper] @@ -226,6 +229,7 @@ def mock_send(*args, **kwargs): # run the circuit eng.flush() prob_dict = eng.backend.get_probabilities([qureg[0], qureg[2], qureg[1]]) + assert prob_dict['000'] == pytest.approx(0.504) assert prob_dict['111'] == pytest.approx(0.482) assert prob_dict['011'] == pytest.approx(0.006) diff --git a/projectq/cengines/_ibm5qubitmapper_test.py b/projectq/cengines/_ibm5qubitmapper_test.py index 2256a27ae..b92c24ae1 100755 --- a/projectq/cengines/_ibm5qubitmapper_test.py +++ b/projectq/cengines/_ibm5qubitmapper_test.py @@ -34,9 +34,10 @@ def mock_send(*args, **kwargs): def test_ibm5qubitmapper_invalid_circuit(): + connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper()]) + engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -51,9 +52,10 @@ def test_ibm5qubitmapper_invalid_circuit(): def test_ibm5qubitmapper_valid_circuit1(): + connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper()]) + engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -70,9 +72,10 @@ def test_ibm5qubitmapper_valid_circuit1(): def test_ibm5qubitmapper_valid_circuit2(): + connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper()]) + engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -89,6 +92,7 @@ def test_ibm5qubitmapper_valid_circuit2(): def test_ibm5qubitmapper_valid_circuit2_ibmqx4(): + connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) backend = DummyEngine(save_commands=True) class FakeIBMBackend(IBMBackend): @@ -100,7 +104,7 @@ class FakeIBMBackend(IBMBackend): backend.is_last_engine = True eng = MainEngine(backend=fake, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper()]) + engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() diff --git a/projectq/setups/ibm_test.py b/projectq/setups/ibm_test.py index b66ca4efb..2188cd42e 100644 --- a/projectq/setups/ibm_test.py +++ b/projectq/setups/ibm_test.py @@ -18,8 +18,19 @@ from projectq.cengines import IBM5QubitMapper, SwapAndCNOTFlipper -def test_ibm_cnot_mapper_in_cengines(): +def test_ibm_cnot_mapper_in_cengines(monkeypatch): import projectq.setups.ibm + def mock_show_devices(*args, **kwargs): + connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return {'ibmq_burlington':{'coupling_map': connections, + 'version': '0.0.0', 'nq': 5}, + 'ibmq_16_melbourne':{'coupling_map': connections, + 'version': '0.0.0', 'nq': 15}, + 'ibmq_qasm_simulator':{'coupling_map': connections, + 'version': '0.0.0', 'nq': 32} + } + monkeypatch.setattr(projectq.setups.ibm, "show_devices", mock_show_devices) engines_5qb=projectq.setups.ibm.get_engine_list(device='ibmq_burlington') engines_15qb=projectq.setups.ibm.get_engine_list(device='ibmq_16_melbourne') engines_simulator=projectq.setups.ibm.get_engine_list(device='ibmq_qasm_simulator') From 9b9a0d0fb887658c374979cd1a1cac9e4063a57d Mon Sep 17 00:00:00 2001 From: dbretaud Date: Sun, 19 Jan 2020 01:33:57 +0000 Subject: [PATCH 35/43] minor fix on comments and python 2.7 compatibility --- projectq/backends/_ibm/_ibm_http_client.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index ca2ccf97d..c858c3b74 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -33,7 +33,7 @@ class IBMQ(Session): def __init__(self, **kwargs): - super().__init__( **kwargs)#Python 2 compatibility + super(IBMQ,self).__init__( **kwargs)#Python 2 compatibility self.backends=dict() self.timeout=5.0 @@ -53,7 +53,6 @@ def get_list_devices(self,verbose=False): r = super().get(urljoin(_api_url, list_device_url), **argument) r.raise_for_status() r_json=r.json() - print(r_json) self.backends=dict() for el in r_json: self.backends[el['backend_name']]={'nq':el['n_qubits'],'coupling_map':el['coupling_map'],'version':el['backend_version']} @@ -105,7 +104,6 @@ def _authenticate(self,token=None): r = super().post(_auth_api_url, **args) r.raise_for_status() r_json=r.json() - print(r_json) self.params.update({'access_token': r_json['id']}) return @@ -145,7 +143,6 @@ def _run(self,info, device): r = super().post(urljoin(_api_url, post_job_url), **argument) r.raise_for_status() r_json=r.json() - print(r.json()) execution_id = r_json["id"] return execution_id @@ -165,17 +162,12 @@ def _handle_sigint_during_get_result(*_): try: signal.signal(signal.SIGINT, _handle_sigint_during_get_result) - print('is online') - print(self.is_online(device)) for retries in range(num_retries): argument={'allow_redirects': True, 'timeout': (self.timeout, None)} r = super().get(urljoin(_api_url, job_status_url), **argument) r.raise_for_status() r_json = r.json() - print(r_json) - print(retries) - print(retries % 60) if r_json['status']=='COMPLETED': return r_json['qObjectResult']['results'][0] elif r_json['status']!='RUNNING': @@ -183,7 +175,7 @@ def _handle_sigint_during_get_result(*_): .format(r_json['status'])) time.sleep(interval) if self.is_online(device) and retries % 60 == 0: - print(self.get_list_devices()) + self.get_list_devices() if not self.is_online(device): raise DeviceOfflineError("Device went offline. The ID of " "your submitted job is {}." From 7fb37771de76d58f61aab35c734be2c9f4d0a58e Mon Sep 17 00:00:00 2001 From: dbretaud Date: Sun, 19 Jan 2020 01:44:56 +0000 Subject: [PATCH 36/43] fix 'super()' call for python 2.7 --- projectq/backends/_ibm/_ibm_http_client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index c858c3b74..d498dc774 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -50,7 +50,7 @@ def get_list_devices(self,verbose=False): """ list_device_url='Network/ibm-q/Groups/open/Projects/main/devices/v/1' argument={'allow_redirects': True, 'timeout': (self.timeout, None)} - r = super().get(urljoin(_api_url, list_device_url), **argument) + r = super(IBMQ,self).get(urljoin(_api_url, list_device_url), **argument) r.raise_for_status() r_json=r.json() self.backends=dict() @@ -101,7 +101,7 @@ def _authenticate(self,token=None): raise Exception('Error with the IBM QE token') self.headers.update({'X-Qx-Client-Application': CLIENT_APPLICATION}) args={'data': None, 'json': {'apiToken': token}, 'timeout': (self.timeout, None)} - r = super().post(_auth_api_url, **args) + r = super(IBMQ,self).post(_auth_api_url, **args) r.raise_for_status() r_json=r.json() self.params.update({'access_token': r_json['id']}) @@ -140,7 +140,7 @@ def _run(self,info, device): 'qobj_id': 'e72443f5-7752-4e32-9ac8-156f1f3fee18'}, 'backend': {'name': device}, 'shots': shots}, 'timeout': (self.timeout, None)} - r = super().post(urljoin(_api_url, post_job_url), **argument) + r = super(IBMQ,self).post(urljoin(_api_url, post_job_url), **argument) r.raise_for_status() r_json=r.json() execution_id = r_json["id"] @@ -165,7 +165,7 @@ def _handle_sigint_during_get_result(*_): for retries in range(num_retries): argument={'allow_redirects': True, 'timeout': (self.timeout, None)} - r = super().get(urljoin(_api_url, job_status_url), **argument) + r = super(IBMQ,self).get(urljoin(_api_url, job_status_url), **argument) r.raise_for_status() r_json = r.json() if r_json['status']=='COMPLETED': From f0f2f6997abeeb571d7cc4be971c100993633969 Mon Sep 17 00:00:00 2001 From: dbretaud Date: Mon, 20 Jan 2020 11:49:16 +0000 Subject: [PATCH 37/43] additional tests --- projectq/backends/_ibm/_ibm.py | 9 +-- projectq/backends/_ibm/_ibm_http_client.py | 2 +- .../backends/_ibm/_ibm_http_client_test.py | 71 +++++++++++++++- projectq/backends/_ibm/_ibm_test.py | 81 +++++++++++++------ projectq/setups/ibm.py | 4 +- projectq/setups/ibm_test.py | 19 ++++- 6 files changed, 148 insertions(+), 38 deletions(-) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index 6ab2c1122..26e69c760 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -165,7 +165,7 @@ def _store(self, cmd): for pos in qb_pos: qb_str += "q[{}], ".format(pos) self.qasm += qb_str[:-2] + ";" - self._json.append({'qubits': [qb_pos], 'name': 'barrier'}) + self._json.append({'qubits': qb_pos, 'name': 'barrier'}) elif isinstance(gate, (Rx, Ry, Rz)): assert get_control_count(cmd) == 0 qb_pos = cmd.qubits[0][0].id @@ -186,8 +186,7 @@ def _store(self, cmd): self.qasm += "\nu2(0,pi/2) q[{}];".format(qb_pos) self._json.append({'qubits': [qb_pos], 'name': 'u2','params': [0, 3.141592653589793]}) else: - raise Exception('Command not authorized. You should run the circuit with the appropriate ibm setup.' - 'Forbidden command: '+str(cmd)) + raise Exception('Command not authorized. You should run the circuit with the appropriate ibm setup.') def _logical_to_physical(self, qb_id): """ @@ -246,7 +245,8 @@ def get_probabilities(self, qureg): probability_dict[mapped_state] = probability else: probability_dict[mapped_state] += probability - + print('QASM') + print(self.qasm) return probability_dict def _run(self): @@ -269,7 +269,6 @@ def _run(self): qasm = ("\ninclude \"qelib1.inc\";\nqreg q[{nq}];\ncreg c[{nq}];" + self.qasm).format(nq=max_qubit_id) info = {} - info['qasms'] = [{'qasm': qasm}] info['json']=self._json info['nq']=max_qubit_id diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index d498dc774..cbb65ddee 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -194,7 +194,7 @@ class DeviceTooSmall(Exception): class DeviceOfflineError(Exception): pass -def show_devices(token,verbose=False): +def show_devices(token=None,verbose=False): """ Access the list of available devices and their properties (ex: for setup configuration) diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index ca4885b82..6e8b91bc5 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -136,6 +136,13 @@ def user_password_input(prompt): token=None, shots=shots, verbose=True) assert res == result + json_qasm['nq']=40 + request_num[0]=0 + with pytest.raises(_ibm_http_client.DeviceTooSmall): + res = _ibm_http_client.send(json_qasm, + device="ibmqx4", + token=None, + shots=shots, verbose=True) def test_send_real_device_offline(monkeypatch): @@ -195,6 +202,64 @@ def raise_for_status(self): shots=shots, verbose=True) +def test_show_device(monkeypatch): + access_token = "access" + user_id = 2016 + class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + def mocked_requests_get(*args, **kwargs): + # Accessing status of device. Return online. + status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' + if args[1] == urljoin(_api_url, status_url): + connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + + def mocked_requests_post(*args, **kwargs): + class MockRequest: + def __init__(self, body="", url=""): + self.body = body + self.url = url + + class MockPostResponse: + def __init__(self, json_data, text=" "): + self.json_data = json_data + self.text = text + self.request = MockRequest() + + def json(self): + return self.json_data + + def raise_for_status(self): + pass + + # Authentication + if (args[1] == _auth_api_url and + kwargs["json"]["apiToken"] == token): + return MockPostResponse({"userId": user_id, "id": access_token}) + + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) + monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) + # Patch login data + token = '12345' + + def user_password_input(prompt): + if prompt == "IBM QE token > ": + return token + + monkeypatch.setattr("getpass.getpass", user_password_input) + assert _ibm_http_client.show_devices() == {'ibmqx4':{'coupling_map': {(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)}, 'version': '0.1.547', 'nq': 32}} + def test_send_that_errors_are_caught(monkeypatch): class MockResponse: def __init__(self, json_data, status_code): @@ -207,7 +272,7 @@ def json(self): def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[1] == urljoin(_api_url_status, status_url): + if args[1] == urljoin(_api_url, status_url): connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) @@ -250,7 +315,7 @@ def json(self): def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[1] == urljoin(_api_url_status, status_url): + if args[1] == urljoin(_api_url, status_url): connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) @@ -292,7 +357,7 @@ def json(self): def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[1] == urljoin(_api_url_status, status_url): + if args[1] == urljoin(_api_url, status_url): connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index a68c4737f..48f103fce 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -21,6 +21,7 @@ from projectq.setups import restrictedgateset from projectq import MainEngine from projectq.backends._ibm import _ibm +from projectq.backends._ibm import _ibm_http_client from projectq.cengines import (TagRemover, LocalOptimizer, AutoReplacer, @@ -98,6 +99,32 @@ def mock_send(*args, **kwargs): dummy.is_last_engine = True eng.next_engine = dummy +def test_ibm_sent_error_2(monkeypatch): + # patch send + def mock_send(*args, **kwargs): + pass + monkeypatch.setattr(_ibm, "send", mock_send) + backend = _ibm.IBMBackend(verbose=True) + mapper = BasicMapperEngine() + res=dict() + for i in range(4): + res[i]=i + mapper.current_mapping = res + eng = MainEngine(backend=backend, + engine_list=[mapper]) + qubit = eng.allocate_qubit() + Rx(math.pi) | qubit + + with pytest.raises(Exception): + S | qubit # no setup to decompose S gate, so not accepted by the Backend + eng.flush() + # atexit sends another FlushGate, therefore we remove the backend: + dummy = DummyEngine() + dummy.is_last_engine = True + eng.next_engine = dummy + + + def test_ibm_retrieve(monkeypatch): # patch send @@ -154,27 +181,22 @@ def mock_retrieve(*args, **kwargs): def test_ibm_backend_functional_test(monkeypatch): - correct_info = {'qasms': [{'qasm': '\ninclude "qelib1.inc";\nqreg q[4];\ncreg c[4];\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];'}], 'json': [{'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793]}, {'qubits': [1, 2], 'name': 'cx'}, {'qubits': [1, 3], 'name': 'cx'}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [11.780972450962]}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [10.995574287564]}, {'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966]}, {'qubits': [1], 'name': 'measure', 'memory': [1]}, {'qubits': [2], 'name': 'measure', 'memory': [2]}, {'qubits': [3], 'name': 'measure', 'memory': [3]}], 'nq': 4, 'shots': 1000, 'maxCredits': 10, 'backend': {'name': 'ibmq_qasm_simulator'}} - """{'qasms': [{'qasm': '\ninclude "qelib1.inc";\nqreg q[4];\ncreg c[4];\nu2(0,pi/2) q[1];' - '\ncx q[1], q[2];\ncx q[1], q[3];\nu3(6.28318530718, 0, 0) q[1];' - '\nu1(11.780972450962) q[1];\nu3(6.28318530718, 0, 0) q[1];' - '\nu1(10.995574287564) q[1];\nu3(0.2, -pi/2, pi/2) q[1];' - '\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];'}], - 'json': [{'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793]}, - {'qubits': [1, 2], 'name': 'cx'}, {'qubits': [1, 3], 'name': 'cx'}, - {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, - {'qubits': [1], 'name': 'u1', 'params': [11.780972450962]}, - {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, - {'qubits': [1], 'name': 'u1', 'params': [10.995574287564]}, - {'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966]}, - {'qubits': [1], 'name': 'measure', 'memory': [1]}, - {'qubits': [2], 'name': 'measure', 'memory': [2]}, - {'qubits': [3], 'name': 'measure', 'memory': [3]}], - 'nq': 4, - 'shots': 1000, - 'maxCredits': 10, - 'backend': {'name': 'ibmq_qasm_simulator'}}""" - + correct_info = {'json': [{'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793]}, + {'qubits': [1, 2], 'name': 'cx'}, {'qubits': [1, 3], 'name': 'cx'}, + {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, + {'qubits': [1], 'name': 'u1', 'params': [11.780972450962]}, + {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, + {'qubits': [1], 'name': 'u1', 'params': [10.995574287564]}, + {'qubits': [1, 2, 3], 'name': 'barrier'}, + {'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966]}, + {'qubits': [1], 'name': 'measure', 'memory': [1]}, + {'qubits': [2], 'name': 'measure', 'memory': [2]}, + {'qubits': [3], 'name': 'measure', 'memory': [3]}], + 'nq': 4, + 'shots': 1000, + 'maxCredits': 10, + 'backend': {'name': 'ibmq_qasm_simulator'}} + #{'qasms': [{'qasm': '\ninclude "qelib1.inc";\nqreg q[4];\ncreg c[4];\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];'}], 'json': [{'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793]}, {'qubits': [1, 2], 'name': 'cx'}, {'qubits': [1, 3], 'name': 'cx'}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [11.780972450962]}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [10.995574287564]}, {'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966]}, {'qubits': [1], 'name': 'measure', 'memory': [1]}, {'qubits': [2], 'name': 'measure', 'memory': [2]}, {'qubits': [3], 'name': 'measure', 'memory': [3]}], 'nq': 4, 'shots': 1000, 'maxCredits': 10, 'backend': {'name': 'ibmq_qasm_simulator'}} def mock_send(*args, **kwargs): assert args[0] == correct_info return {'data': {'counts': {'0x0': 504, '0x2': 8, '0xc': 6, '0xe': 482}}, @@ -210,7 +232,7 @@ def mock_send(*args, **kwargs): mapper.current_mapping = res ibm_setup=[mapper] setup=restrictedgateset.get_engine_list(one_qubit_gates=(Rx,Ry,Rz,H), - two_qubit_gates=(CNOT,)) + two_qubit_gates=(CNOT,),other_gates=(Barrier,)) setup.extend(ibm_setup) eng = MainEngine(backend=backend, engine_list=setup) #4 qubits circuit is run, but first is unused to test ability for get_probability @@ -228,10 +250,17 @@ def mock_send(*args, **kwargs): All(Measure) | qureg # run the circuit eng.flush() - prob_dict = eng.backend.get_probabilities([qureg[0], qureg[2], qureg[1]]) - assert prob_dict['000'] == pytest.approx(0.504) - assert prob_dict['111'] == pytest.approx(0.482) - assert prob_dict['011'] == pytest.approx(0.006) + prob_dict = eng.backend.get_probabilities([qureg[2], qureg[1]]) + assert prob_dict['00'] == pytest.approx(0.512) + assert prob_dict['11'] == pytest.approx(0.488) + result="\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];" + result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];" + result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];" + result+="\nbarrier q[1], q[2], q[3];" + result+="\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];" + result+="\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];" + + assert eng.backend.get_qasm() == result with pytest.raises(RuntimeError): eng.backend.get_probabilities(eng.allocate_qubit()) diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index 154cc2a42..379d14d76 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -26,7 +26,7 @@ import projectq import projectq.setups.decompositions from projectq.setups import restrictedgateset -from projectq.ops import (Rx, Ry, Rz, H, CNOT) +from projectq.ops import (Rx, Ry, Rz, H, CNOT, Barrier) from projectq.cengines import (TagRemover, LocalOptimizer, AutoReplacer, @@ -83,7 +83,7 @@ def get_engine_list(token=None,device=None): #in the backend (until the implementation of the U1,U2,U3) #available gates decomposable now for U1,U2,U3: Rx,Ry,Rz and H setup=restrictedgateset.get_engine_list(one_qubit_gates=(Rx,Ry,Rz,H), - two_qubit_gates=(CNOT,)) + two_qubit_gates=(CNOT,),other_gates=(Barrier,)) setup.extend(ibm_setup) return setup diff --git a/projectq/setups/ibm_test.py b/projectq/setups/ibm_test.py index 2188cd42e..9f260528d 100644 --- a/projectq/setups/ibm_test.py +++ b/projectq/setups/ibm_test.py @@ -16,7 +16,7 @@ import projectq from projectq import MainEngine from projectq.cengines import IBM5QubitMapper, SwapAndCNOTFlipper - +import pytest def test_ibm_cnot_mapper_in_cengines(monkeypatch): import projectq.setups.ibm @@ -38,3 +38,20 @@ def mock_show_devices(*args, **kwargs): assert len(engines_15qb)==16 assert len(engines_simulator)==13 + + +def test_ibm_errors(monkeypatch): + import projectq.setups.ibm + def mock_show_devices(*args, **kwargs): + connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return {'ibmq_imaginary':{'coupling_map': connections, + 'version': '0.0.0', 'nq': 6}} + monkeypatch.setattr(projectq.setups.ibm, "show_devices", mock_show_devices) + with pytest.raises(projectq.setups.ibm.DeviceOfflineError): + engines_5qb=projectq.setups.ibm.get_engine_list(device='ibmq_burlington') + with pytest.raises(projectq.setups.ibm.DeviceNotHandledError): + engines_6qb=projectq.setups.ibm.get_engine_list(device='ibmq_imaginary') + + + From 48c772180153067a0f0e336e74a3091701c2e21f Mon Sep 17 00:00:00 2001 From: dbretaud Date: Tue, 21 Jan 2020 18:58:08 +0000 Subject: [PATCH 38/43] python2.7 fix --- projectq/backends/_ibm/_ibm_test.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index 48f103fce..e43272717 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -222,6 +222,7 @@ def mock_send(*args, **kwargs): monkeypatch.setattr(_ibm, "send", mock_send) backend = _ibm.IBMBackend(verbose=True,num_runs=1000) + import sys # no circuit has been executed -> raises exception with pytest.raises(RuntimeError): backend.get_probabilities([]) @@ -254,8 +255,12 @@ def mock_send(*args, **kwargs): assert prob_dict['00'] == pytest.approx(0.512) assert prob_dict['11'] == pytest.approx(0.488) result="\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];" - result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];" - result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];" + if sys.version_info.major == 3: + result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];" + result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];" + else: + result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972451) q[1];" + result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(10.9955742876) q[1];" result+="\nbarrier q[1], q[2], q[3];" result+="\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];" result+="\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];" From 776e8b5717d832b4af390b1dbc438eb007a6f6e7 Mon Sep 17 00:00:00 2001 From: dbretaud Date: Fri, 24 Jan 2020 13:57:21 +0000 Subject: [PATCH 39/43] increase coverage, fix a print statement --- projectq/backends/_ibm/_ibm.py | 2 - .../backends/_ibm/_ibm_http_client_test.py | 64 ++++++------------- projectq/backends/_ibm/_ibm_test.py | 6 -- 3 files changed, 20 insertions(+), 52 deletions(-) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index 26e69c760..fd4235d8d 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -245,8 +245,6 @@ def get_probabilities(self, qureg): probability_dict[mapped_state] = probability else: probability_dict[mapped_state] += probability - print('QASM') - print(self.qasm) return probability_dict def _run(self): diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index 6e8b91bc5..b2ad5bd74 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -145,6 +145,21 @@ def user_password_input(prompt): shots=shots, verbose=True) +def test_no_password_given(monkeypatch): + token = '' + json_qasm = '' + def user_password_input(prompt): + if prompt == "IBM QE token > ": + return token + + monkeypatch.setattr("getpass.getpass", user_password_input) + + with pytest.raises(Exception): + res = _ibm_http_client.send(json_qasm, + device="ibmqx4", + token=None, + shots=1, verbose=True) + def test_send_real_device_offline(monkeypatch): token = '12345' access_token = "access" @@ -263,25 +278,12 @@ def user_password_input(prompt): def test_send_that_errors_are_caught(monkeypatch): class MockResponse: def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code - - def json(self): - return self.json_data - - def mocked_requests_get(*args, **kwargs): - # Accessing status of device. Return online. - status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[1] == urljoin(_api_url, status_url): - connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), - (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) - + pass + def mocked_requests_post(*args, **kwargs): # Test that this error gets caught raise requests.exceptions.HTTPError - monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) # Patch login data token = '12345' @@ -306,25 +308,12 @@ def user_password_input(prompt): def test_send_that_errors_are_caught2(monkeypatch): class MockResponse: def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code - - def json(self): - return self.json_data - - def mocked_requests_get(*args, **kwargs): - # Accessing status of device. Return online. - status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[1] == urljoin(_api_url, status_url): - connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), - (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) - + pass + def mocked_requests_post(*args, **kwargs): # Test that this error gets caught raise requests.exceptions.RequestException - monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) # Patch login data token = '12345' @@ -348,25 +337,12 @@ def user_password_input(prompt): def test_send_that_errors_are_caught3(monkeypatch): class MockResponse: def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code - - def json(self): - return self.json_data - - def mocked_requests_get(*args, **kwargs): - # Accessing status of device. Return online. - status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[1] == urljoin(_api_url, status_url): - connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), - (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + pass def mocked_requests_post(*args, **kwargs): # Test that this error gets caught raise KeyError - monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) # Patch login data token = '12345' diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index e43272717..31262d057 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -100,10 +100,6 @@ def mock_send(*args, **kwargs): eng.next_engine = dummy def test_ibm_sent_error_2(monkeypatch): - # patch send - def mock_send(*args, **kwargs): - pass - monkeypatch.setattr(_ibm, "send", mock_send) backend = _ibm.IBMBackend(verbose=True) mapper = BasicMapperEngine() res=dict() @@ -117,8 +113,6 @@ def mock_send(*args, **kwargs): with pytest.raises(Exception): S | qubit # no setup to decompose S gate, so not accepted by the Backend - eng.flush() - # atexit sends another FlushGate, therefore we remove the backend: dummy = DummyEngine() dummy.is_last_engine = True eng.next_engine = dummy From e7a6899c63c29a0a02b6dd75eae3c027ba557b99 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 4 Feb 2020 10:21:18 +0100 Subject: [PATCH 40/43] Some minor stylistic adjustments --- projectq/backends/_ibm/_ibm.py | 7 ++-- projectq/backends/_ibm/_ibm_http_client.py | 21 ++++++---- projectq/setups/ibm.py | 48 +++++++++++----------- 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index fd4235d8d..6486ab4d0 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -224,7 +224,7 @@ def get_probabilities(self, qureg): Returns: probability_dict (dict): Dictionary mapping n-bit strings to - probabilities. + probabilities. Raises: RuntimeError: If no data is available (i.e., if the circuit has @@ -251,8 +251,8 @@ def _run(self): """ Run the circuit. - Send the circuit via a non documented IBM API (using JSON written circuits) using the provided user - data / ask for the user token. + Send the circuit via a non documented IBM API (using JSON written + circuits) using the provided user data / ask for the user token. """ # finally: add measurements (no intermediate measurements are allowed) for measured_id in self._measured_ids: @@ -336,4 +336,3 @@ def receive(self, command_list): else: self._run() self._reset() - diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index cbb65ddee..5769ab37f 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -43,6 +43,7 @@ def get_list_devices(self,verbose=False): Args: verbose (bool): print the returned dictionnary if True + Returns: (dict) backends dictionary by name device, containing the qubit size 'nq', the coupling map 'coupling_map' as well as the device @@ -63,10 +64,11 @@ def get_list_devices(self,verbose=False): def is_online(self,device): """ - check if the device is in the list of available IBM backends + Check if the device is in the list of available IBM backends. Args: device (str): name of the device to check + Returns: (bool) True if device is available, False otherwise """ @@ -74,11 +76,12 @@ def is_online(self,device): def can_run_experiment(self,info,device): """ - check if the device is big enough to run the code + Check if the device is big enough to run the code. Args: info (dict): dictionary sent by the backend containing the code to run device (str): name of the ibm device to use + Returns: (tuple): (bool) True if device is big enough, False otherwise (int) maximum number of qubit available on the device @@ -93,7 +96,6 @@ def _authenticate(self,token=None): """ Args: token (str): IBM quantum experience user API token. - Returns: """ if token is None: token = getpass.getpass(prompt="IBM QE token > ") @@ -196,13 +198,14 @@ class DeviceOfflineError(Exception): def show_devices(token=None,verbose=False): """ - Access the list of available devices and their properties (ex: for setup configuration) + Access the list of available devices and their properties (ex: for setup + configuration) Args: token (str): IBM quantum experience user API token. verbose (bool): If True, additional information is printed - Return: + Returns: (list) list of available devices and their properties """ ibmq_session=IBMQ() @@ -219,7 +222,7 @@ def retrieve(device, token, jobid, num_retries=3000, token (str): IBM quantum experience user API token. jobid (str): Id of the job to retrieve - Return: + Returns: (dict) result form the IBMQ server """ ibmq_session=IBMQ() @@ -244,7 +247,7 @@ def send(info, device='ibmq_qasm_simulator',token=None, measurement statistics. Otherwise, the backend simply registers one measurement result (same behavior as the projectq Simulator). - Return: + Returns: (dict) result form the IBMQ server """ @@ -255,8 +258,8 @@ def send(info, device='ibmq_qasm_simulator',token=None, info['shots']=shots if verbose: print("- Authenticating...") - if token is not None: - print('user API token: '+token) + if token is not None: + print('user API token: '+token) ibmq_session._authenticate(token) # check if the device is online diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index 379d14d76..ded5eafb4 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -18,9 +18,9 @@ ->the ibmq online simulator ->the melbourne 15 qubit device -It provides the `engine_list` for the `MainEngine' based on the requested device. -Decompose the circuit into a Rx/Ry/Rz/H/CNOT gate set that will be translated -in the backend in the U1/U2/U3/CX gate set. +It provides the `engine_list` for the `MainEngine' based on the requested +device. Decompose the circuit into a Rx/Ry/Rz/H/CNOT gate set that will be +translated in the backend in the U1/U2/U3/CX gate set. """ import projectq @@ -38,50 +38,52 @@ from projectq.backends._ibm._ibm_http_client import show_devices def get_engine_list(token=None,device=None): - #access to the hardware properties via show_devices - #can be extended to take into account gate fidelities, new available gate, etc.. + # Access to the hardware properties via show_devices + # Can also be extended to take into account gate fidelities, new available + # gate, etc.. devices=show_devices(token) ibm_setup=[] if device not in devices: raise DeviceOfflineError('Error when configuring engine list: device ' - 'requested for Backend not connected') + 'requested for Backend not connected') elif devices[device]['nq']==5: - #The requested device is a 5 qubit processor - #Obtain the coupling map specific to the device + # The requested device is a 5 qubit processor + # Obtain the coupling map specific to the device coupling_map=devices[device]['coupling_map'] coupling_map=ListToSet(coupling_map) Mapper=IBM5QubitMapper(coupling_map) ibm_setup=[Mapper,SwapAndCNOTFlipper(coupling_map),LocalOptimizer(10)] elif device=='ibmq_qasm_simulator': - #the 32 qubit online simulator doesn't need a specific mapping for gates. - #can also run wider gateset but this setup keep the restrictedgateset setup for - #coherence + # The 32 qubit online simulator doesn't need a specific mapping for + # gates. Can also run wider gateset but this setup keep the + # restrictedgateset setup for coherence mapper = BasicMapperEngine() - #Note: Manual Mapper doesn't work, because its map is updated only if gates are applied - #if gates in the register are not used, then it will lead to state errors + # Note: Manual Mapper doesn't work, because its map is updated only if + # gates are applied if gates in the register are not used, then it + # will lead to state errors res=dict() for i in range(devices[device]['nq']): res[i]=i mapper.current_mapping = res ibm_setup=[mapper] elif device=='ibmq_16_melbourne': - #Only 15 qubits available on this ibmqx2 unit(in particular qubit 7 on the grid), - #therefore need custom grid mapping + # Only 15 qubits available on this ibmqx2 unit(in particular qubit 7 + # on the grid), therefore need custom grid mapping grid_to_physical = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 15, 8: 14, 9: 13, 10: 12, 11: 11, 12: 10, 13: 9, 14: 8, 15: 7} coupling_map=devices[device]['coupling_map'] coupling_map=ListToSet(coupling_map) ibm_setup=[GridMapper(2, 8, grid_to_physical),LocalOptimizer(5),SwapAndCNOTFlipper(coupling_map),LocalOptimizer(5)] else: - #if there is an online device not handled into ProjectQ it's not too bad, - #the engine_list can be constructed manually with the appropriate mapper - #and the 'coupling_map' parameter + # If there is an online device not handled into ProjectQ it's not too + # bad, the engine_list can be constructed manually with the + # appropriate mapper and the 'coupling_map' parameter raise DeviceNotHandledError('Device not yet fully handled by ProjectQ') - #Most IBM devices accept U1,U2,U3,CX gates. - #Most gates need to be decomposed into a subset that is manually converted - #in the backend (until the implementation of the U1,U2,U3) - #available gates decomposable now for U1,U2,U3: Rx,Ry,Rz and H + # Most IBM devices accept U1,U2,U3,CX gates. + # Most gates need to be decomposed into a subset that is manually converted + # in the backend (until the implementation of the U1,U2,U3) + # available gates decomposable now for U1,U2,U3: Rx,Ry,Rz and H setup=restrictedgateset.get_engine_list(one_qubit_gates=(Rx,Ry,Rz,H), two_qubit_gates=(CNOT,),other_gates=(Barrier,)) setup.extend(ibm_setup) @@ -97,4 +99,4 @@ def ListToSet(coupling_list): result=[] for el in coupling_list: result.append(tuple(el)) - return set(result) \ No newline at end of file + return set(result) From 67cab0fc6e4a01b2f387ca30a540195d0e90749c Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 4 Feb 2020 10:43:31 +0100 Subject: [PATCH 41/43] Reindent files and fix some linting warnings/errors --- projectq/backends/_ibm/_ibm_http_client.py | 297 ++++++++++------- .../backends/_ibm/_ibm_http_client_test.py | 305 ++++++++++++------ projectq/backends/_ibm/_ibm_test.py | 289 +++++++++++------ projectq/cengines/_ibm5qubitmapper.py | 53 +-- projectq/cengines/_ibm5qubitmapper_test.py | 47 ++- projectq/setups/ibm.py | 84 +++-- projectq/setups/ibm_test.py | 69 ++-- 7 files changed, 733 insertions(+), 411 deletions(-) diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 5769ab37f..3e6ee9f51 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -16,28 +16,31 @@ # api documentation does not exist and has to be deduced from the qiskit code source # at: https://github.com/Qiskit/qiskit-ibmq-provider -import requests import getpass -import json -import signal -import sys import time +import signal +import requests from requests.compat import urljoin from requests import Session -_auth_api_url = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' -_api_url = 'https://api.quantum-computing.ibm.com/api/' +_AUTH_API_URL = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' +_API_URL = 'https://api.quantum-computing.ibm.com/api/' -CLIENT_APPLICATION = 'ibmqprovider/0.4.4'#TODO: call to get the API version automatically +# TODO: call to get the API version automatically +CLIENT_APPLICATION = 'ibmqprovider/0.4.4' class IBMQ(Session): + """ + Manage a session between ProjectQ and the IBMQ web API. + """ + def __init__(self, **kwargs): - super(IBMQ,self).__init__( **kwargs)#Python 2 compatibility - self.backends=dict() - self.timeout=5.0 + super(IBMQ, self).__init__(**kwargs) # Python 2 compatibility + self.backends = dict() + self.timeout = 5.0 - def get_list_devices(self,verbose=False): + def get_list_devices(self, verbose=False): """ Get the list of available IBM backends with their properties @@ -45,24 +48,30 @@ def get_list_devices(self,verbose=False): verbose (bool): print the returned dictionnary if True Returns: - (dict) backends dictionary by name device, containing the qubit size 'nq', - the coupling map 'coupling_map' as well as the device - version 'version' + (dict) backends dictionary by name device, containing the qubit + size 'nq', the coupling map 'coupling_map' as well as the + device version 'version' """ - list_device_url='Network/ibm-q/Groups/open/Projects/main/devices/v/1' - argument={'allow_redirects': True, 'timeout': (self.timeout, None)} - r = super(IBMQ,self).get(urljoin(_api_url, list_device_url), **argument) - r.raise_for_status() - r_json=r.json() - self.backends=dict() + list_device_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' + argument = {'allow_redirects': True, 'timeout': (self.timeout, None)} + request = super(IBMQ, self).get(urljoin(_API_URL, list_device_url), + **argument) + request.raise_for_status() + r_json = request.json() + self.backends = dict() for el in r_json: - self.backends[el['backend_name']]={'nq':el['n_qubits'],'coupling_map':el['coupling_map'],'version':el['backend_version']} - if verbose==True: + self.backends[el['backend_name']] = { + 'nq': el['n_qubits'], + 'coupling_map': el['coupling_map'], + 'version': el['backend_version'] + } + + if verbose: print('- List of IBMQ devices available:') print(self.backends) return self.backends - def is_online(self,device): + def is_online(self, device): """ Check if the device is in the list of available IBM backends. @@ -74,12 +83,13 @@ def is_online(self,device): """ return device in self.backends - def can_run_experiment(self,info,device): + def can_run_experiment(self, info, device): """ Check if the device is big enough to run the code. Args: - info (dict): dictionary sent by the backend containing the code to run + info (dict): dictionary sent by the backend containing the code to + run device (str): name of the ibm device to use Returns: @@ -88,115 +98,161 @@ def can_run_experiment(self,info,device): (int) number of qubit needed for the circuit """ - nb_qubit_max=self.backends[device]['nq'] - nb_qubit_needed=info['nq'] - return nb_qubit_needed<=nb_qubit_max,nb_qubit_max,nb_qubit_needed + nb_qubit_max = self.backends[device]['nq'] + nb_qubit_needed = info['nq'] + return nb_qubit_needed <= nb_qubit_max, nb_qubit_max, nb_qubit_needed - def _authenticate(self,token=None): + def _authenticate(self, token=None): """ Args: token (str): IBM quantum experience user API token. """ if token is None: token = getpass.getpass(prompt="IBM QE token > ") - if len(token)==0: + if len(token) == 0: raise Exception('Error with the IBM QE token') self.headers.update({'X-Qx-Client-Application': CLIENT_APPLICATION}) - args={'data': None, 'json': {'apiToken': token}, 'timeout': (self.timeout, None)} - r = super(IBMQ,self).post(_auth_api_url, **args) - r.raise_for_status() - r_json=r.json() - self.params.update({'access_token': r_json['id']}) - return - - - def _run(self,info, device): - post_job_url='Network/ibm-q/Groups/open/Projects/main/Jobs' - shots=info['shots'] - nq=info['nq'] - mq=self.backends[device]['nq'] - version=self.backends[device]['version'] - instructions=info['json'] - maxcredit=info['maxCredits'] - c_label=[] - q_label=[] + args = { + 'data': None, + 'json': { + 'apiToken': token + }, + 'timeout': (self.timeout, None) + } + request = super(IBMQ, self).post(_AUTH_API_URL, **args) + request.raise_for_status() + r_json = request.json() + self.params.update({'access_token': r_json['id']}) + + def _run(self, info, device): + post_job_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs' + shots = info['shots'] + nq = info['nq'] + mq = self.backends[device]['nq'] + version = self.backends[device]['version'] + instructions = info['json'] + maxcredit = info['maxCredits'] + c_label = [] + q_label = [] for i in range(nq): - c_label.append(['c',i]) + c_label.append(['c', i]) for i in range(mq): - q_label.append(['q',i]) - experiment=[{'header': {'qreg_sizes': [['q', mq]], 'n_qubits': mq, - 'memory_slots': nq, 'creg_sizes': [['c', nq]], - 'clbit_labels': c_label, 'qubit_labels': q_label, - 'name': 'circuit0'}, - 'config': {'n_qubits': mq, 'memory_slots': nq}, - 'instructions':instructions}] - #Note: qobj_id is not necessary in projectQ, so fixed string for now - argument={'data': None, - 'json': {'qObject': {'type': 'QASM', 'schema_version': '1.1.0', - 'config': {'shots': shots, 'max_credits': maxcredit, - 'n_qubits': mq, 'memory_slots': nq, - 'memory': False, 'parameter_binds': []}, - 'experiments': experiment, - 'header': {'backend_version': version, - 'backend_name': device}, - 'qobj_id': 'e72443f5-7752-4e32-9ac8-156f1f3fee18'}, - 'backend': {'name': device}, 'shots': shots}, - 'timeout': (self.timeout, None)} - r = super(IBMQ,self).post(urljoin(_api_url, post_job_url), **argument) - r.raise_for_status() - r_json=r.json() + q_label.append(['q', i]) + experiment = [{ + 'header': { + 'qreg_sizes': [['q', mq]], + 'n_qubits': mq, + 'memory_slots': nq, + 'creg_sizes': [['c', nq]], + 'clbit_labels': c_label, + 'qubit_labels': q_label, + 'name': 'circuit0' + }, + 'config': { + 'n_qubits': mq, + 'memory_slots': nq + }, + 'instructions': instructions + }] + # Note: qobj_id is not necessary in projectQ, so fixed string for now + argument = { + 'data': None, + 'json': { + 'qObject': { + 'type': 'QASM', + 'schema_version': '1.1.0', + 'config': { + 'shots': shots, + 'max_credits': maxcredit, + 'n_qubits': mq, + 'memory_slots': nq, + 'memory': False, + 'parameter_binds': [] + }, + 'experiments': experiment, + 'header': { + 'backend_version': version, + 'backend_name': device + }, + 'qobj_id': 'e72443f5-7752-4e32-9ac8-156f1f3fee18' + }, + 'backend': { + 'name': device + }, + 'shots': shots + }, + 'timeout': (self.timeout, None) + } + request = super(IBMQ, self).post(urljoin(_API_URL, post_job_url), + **argument) + request.raise_for_status() + r_json = request.json() execution_id = r_json["id"] return execution_id - def _get_result(self,device, execution_id, num_retries=3000, - interval=1, verbose=False): - - job_status_url='Network/ibm-q/Groups/open/Projects/main/Jobs/'+execution_id - + def _get_result(self, + device, + execution_id, + num_retries=3000, + interval=1, + verbose=False): + + job_status_url = ('Network/ibm-q/Groups/open/Projects/main/Jobs/' + + execution_id) + if verbose: print("Waiting for results. [Job ID: {}]".format(execution_id)) original_sigint_handler = signal.getsignal(signal.SIGINT) def _handle_sigint_during_get_result(*_): - raise Exception("Interrupted. The ID of your submitted job is {}." - .format(execution_id)) + raise Exception( + "Interrupted. The ID of your submitted job is {}.".format( + execution_id)) try: signal.signal(signal.SIGINT, _handle_sigint_during_get_result) for retries in range(num_retries): - - argument={'allow_redirects': True, 'timeout': (self.timeout, None)} - r = super(IBMQ,self).get(urljoin(_api_url, job_status_url), **argument) - r.raise_for_status() - r_json = r.json() - if r_json['status']=='COMPLETED': + + argument = { + 'allow_redirects': True, + 'timeout': (self.timeout, None) + } + request = super(IBMQ, + self).get(urljoin(_API_URL, job_status_url), + **argument) + request.raise_for_status() + r_json = request.json() + if r_json['status'] == 'COMPLETED': return r_json['qObjectResult']['results'][0] - elif r_json['status']!='RUNNING': - raise Exception("Error while running the code: {}." - .format(r_json['status'])) + if r_json['status'] != 'RUNNING': + raise Exception("Error while running the code: {}.".format( + r_json['status'])) time.sleep(interval) if self.is_online(device) and retries % 60 == 0: self.get_list_devices() if not self.is_online(device): - raise DeviceOfflineError("Device went offline. The ID of " - "your submitted job is {}." - .format(execution_id)) + raise DeviceOfflineError( + "Device went offline. The ID of " + "your submitted job is {}.".format(execution_id)) finally: if original_sigint_handler is not None: signal.signal(signal.SIGINT, original_sigint_handler) - raise Exception("Timeout. The ID of your submitted job is {}." - .format(execution_id)) + raise Exception("Timeout. The ID of your submitted job is {}.".format( + execution_id)) + class DeviceTooSmall(Exception): pass + class DeviceOfflineError(Exception): pass -def show_devices(token=None,verbose=False): + +def show_devices(token=None, verbose=False): """ Access the list of available devices and their properties (ex: for setup configuration) @@ -208,12 +264,17 @@ def show_devices(token=None,verbose=False): Returns: (list) list of available devices and their properties """ - ibmq_session=IBMQ() + ibmq_session = IBMQ() ibmq_session._authenticate(token=token) return ibmq_session.get_list_devices(verbose=verbose) -def retrieve(device, token, jobid, num_retries=3000, - interval=1, verbose=False): + +def retrieve(device, + token, + jobid, + num_retries=3000, + interval=1, + verbose=False): """ Retrieves a previously run job by its ID. @@ -221,20 +282,28 @@ def retrieve(device, token, jobid, num_retries=3000, device (str): Device on which the code was run / is running. token (str): IBM quantum experience user API token. jobid (str): Id of the job to retrieve - + Returns: (dict) result form the IBMQ server """ - ibmq_session=IBMQ() + ibmq_session = IBMQ() ibmq_session._authenticate(token) ibmq_session.get_list_devices(verbose) - res = ibmq_session._get_result(device, jobid, num_retries=num_retries, - interval=interval, verbose=verbose) + res = ibmq_session._get_result(device, + jobid, + num_retries=num_retries, + interval=interval, + verbose=verbose) return res -def send(info, device='ibmq_qasm_simulator',token=None, - shots=None, num_retries=3000, interval=1, verbose=False): +def send(info, + device='ibmq_qasm_simulator', + token=None, + shots=None, + num_retries=3000, + interval=1, + verbose=False): """ Sends QASM through the IBM API and runs the quantum circuit. @@ -252,14 +321,14 @@ def send(info, device='ibmq_qasm_simulator',token=None, """ try: - ibmq_session=IBMQ() - #shots argument deprecated, as already + ibmq_session = IBMQ() + # Shots argument deprecated, as already if shots is not None: - info['shots']=shots + info['shots'] = shots if verbose: print("- Authenticating...") if token is not None: - print('user API token: '+token) + print('user API token: ' + token) ibmq_session._authenticate(token) # check if the device is online @@ -269,19 +338,25 @@ def send(info, device='ibmq_qasm_simulator',token=None, print("The device is offline (for maintenance?). Use the " "simulator instead or try again later.") raise DeviceOfflineError("Device is offline.") - + # check if the device has enough qubit to run the code - runnable,qmax,qneeded = ibmq_session.can_run_experiment(info,device) + runnable, qmax, qneeded = ibmq_session.can_run_experiment(info, device) if not runnable: - print("The device is too small ({} qubits available) for the code requested({} qubits needed" - "Try to look for another device with more qubits".format(qmax,qneeded)) + print( + ("The device is too small ({} qubits available) for the code " + + "requested({} qubits needed) Try to look for another " + + "device with more qubits").format(qmax, qneeded)) raise DeviceTooSmall("Device is too small.") if verbose: print("- Running code: {}".format(info)) execution_id = ibmq_session._run(info, device) if verbose: print("- Waiting for results...") - res = ibmq_session._get_result(device, execution_id,num_retries=num_retries,interval=interval, verbose=verbose) + res = ibmq_session._get_result(device, + execution_id, + num_retries=num_retries, + interval=interval, + verbose=verbose) if verbose: print("- Done.") return res diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index b2ad5bd74..256e90c41 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_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.backends._ibm_http_client._ibm.py.""" import json @@ -28,12 +27,20 @@ def no_requests(monkeypatch): monkeypatch.delattr("requests.sessions.Session.request") -_api_url = 'https://api.quantum-computing.ibm.com/api/' -_auth_api_url = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' +_API_URL = 'https://api.quantum-computing.ibm.com/api/' +_AUTH_API_URL = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' + def test_send_real_device_online_verbose(monkeypatch): - json_qasm = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, - 'json': 'instructions','maxCredits': 10,'nq': 1} + json_qasm = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + 'nq': 1 + } name = 'projectq_test' token = '12345' access_token = "access" @@ -70,23 +77,38 @@ def raise_for_status(self): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if (args[1] == urljoin(_api_url, status_url) and - (request_num[0] == 1 or request_num[0] == 4)): + if (args[1] == urljoin(_API_URL, status_url) + and (request_num[0] == 1 or request_num[0] == 4)): request_num[0] += 1 - connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), - (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{ + 'backend_name': 'ibmqx4', + 'coupling_map': connections, + 'backend_version': '0.1.547', + 'n_qubits': 32 + }], 200) # Getting result - elif (args[1] == urljoin(_api_url, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id)) and not - result_ready[0] and request_num[0] == 3): + elif (args[1] == urljoin( + _API_URL, + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}". + format(execution_id=execution_id)) and not result_ready[0] + and request_num[0] == 3): result_ready[0] = True request_num[0] += 1 return MockResponse({"status": "RUNNING"}, 200) - elif (args[1] == urljoin(_api_url, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id)) and - result_ready[0] and request_num[0] == 5): - return MockResponse({'qObjectResult':{"results": [result]},"status": "COMPLETED"}, 200) + elif (args[1] == urljoin( + _API_URL, + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}". + format(execution_id=execution_id)) and result_ready[0] + and request_num[0] == 5): + return MockResponse( + { + 'qObjectResult': { + "results": [result] + }, + "status": "COMPLETED" + }, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -105,22 +127,21 @@ def json(self): def raise_for_status(self): pass - jobs_url='Network/ibm-q/Groups/open/Projects/main/Jobs' + + jobs_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs' # Authentication - if (args[1] == _auth_api_url and - kwargs["json"]["apiToken"] == token and - request_num[0] == 0): + if (args[1] == _AUTH_API_URL and kwargs["json"]["apiToken"] == token + and request_num[0] == 0): request_num[0] += 1 return MockPostResponse({"userId": user_id, "id": access_token}) # Run code - elif (args[1] == urljoin(_api_url, jobs_url) and - kwargs["data"] == None and - kwargs["json"]["backend"]["name"] == device and - kwargs["json"]["qObject"]['config']['shots'] == shots and - request_num[0] == 2): + elif (args[1] == urljoin(_API_URL, jobs_url) and kwargs["data"] is None + and kwargs["json"]["backend"]["name"] == device + and kwargs["json"]["qObject"]['config']['shots'] == shots + and request_num[0] == 2): request_num[0] += 1 return MockPostResponse({"id": execution_id}) - + monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) @@ -134,20 +155,23 @@ def user_password_input(prompt): res = _ibm_http_client.send(json_qasm, device="ibmqx4", token=None, - shots=shots, verbose=True) + shots=shots, + verbose=True) assert res == result - json_qasm['nq']=40 - request_num[0]=0 + json_qasm['nq'] = 40 + request_num[0] = 0 with pytest.raises(_ibm_http_client.DeviceTooSmall): res = _ibm_http_client.send(json_qasm, - device="ibmqx4", - token=None, - shots=shots, verbose=True) + device="ibmqx4", + token=None, + shots=shots, + verbose=True) def test_no_password_given(monkeypatch): - token = '' - json_qasm = '' + token = '' + json_qasm = '' + def user_password_input(prompt): if prompt == "IBM QE token > ": return token @@ -156,14 +180,17 @@ def user_password_input(prompt): with pytest.raises(Exception): res = _ibm_http_client.send(json_qasm, - device="ibmqx4", - token=None, - shots=1, verbose=True) + device="ibmqx4", + token=None, + shots=1, + verbose=True) + def test_send_real_device_offline(monkeypatch): token = '12345' access_token = "access" user_id = 2016 + def mocked_requests_get(*args, **kwargs): class MockResponse: def __init__(self, json_data, status_code): @@ -178,9 +205,9 @@ def raise_for_status(self): # Accessing status of device. Return offline. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[1] == urljoin(_api_url, status_url): + if args[1] == urljoin(_API_URL, status_url): return MockResponse({}, 200) - + def mocked_requests_post(*args, **kwargs): class MockRequest: def __init__(self, body="", url=""): @@ -198,28 +225,38 @@ def json(self): def raise_for_status(self): pass + # Authentication - if (args[1] == _auth_api_url and kwargs["json"]["apiToken"] == token): + if (args[1] == _AUTH_API_URL and kwargs["json"]["apiToken"] == token): return MockPostResponse({"userId": user_id, "id": access_token}) monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) shots = 1 - token='12345' - json_qasm = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, - 'json': 'instructions','maxCredits': 10,'nq': 1} + token = '12345' + json_qasm = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + 'nq': 1 + } name = 'projectq_test' with pytest.raises(_ibm_http_client.DeviceOfflineError): _ibm_http_client.send(json_qasm, device="ibmqx4", token=token, - shots=shots, verbose=True) + shots=shots, + verbose=True) def test_show_device(monkeypatch): access_token = "access" user_id = 2016 + class MockResponse: def __init__(self, json_data, status_code): self.json_data = json_data @@ -234,10 +271,15 @@ def raise_for_status(self): def mocked_requests_get(*args, **kwargs): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[1] == urljoin(_api_url, status_url): - connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), - (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) + if args[1] == urljoin(_API_URL, status_url): + connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{ + 'backend_name': 'ibmqx4', + 'coupling_map': connections, + 'backend_version': '0.1.547', + 'n_qubits': 32 + }], 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -258,8 +300,7 @@ def raise_for_status(self): pass # Authentication - if (args[1] == _auth_api_url and - kwargs["json"]["apiToken"] == token): + if (args[1] == _AUTH_API_URL and kwargs["json"]["apiToken"] == token): return MockPostResponse({"userId": user_id, "id": access_token}) monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) @@ -272,14 +313,21 @@ def user_password_input(prompt): return token monkeypatch.setattr("getpass.getpass", user_password_input) - assert _ibm_http_client.show_devices() == {'ibmqx4':{'coupling_map': {(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), - (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)}, 'version': '0.1.547', 'nq': 32}} + assert _ibm_http_client.show_devices() == { + 'ibmqx4': { + 'coupling_map': {(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)}, + 'version': '0.1.547', + 'nq': 32 + } + } + def test_send_that_errors_are_caught(monkeypatch): class MockResponse: def __init__(self, json_data, status_code): pass - + def mocked_requests_post(*args, **kwargs): # Test that this error gets caught raise requests.exceptions.HTTPError @@ -294,22 +342,28 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 - json_qasm = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, - 'json': 'instructions','maxCredits': 10,'nq': 1} + json_qasm = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + 'nq': 1 + } name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", token=None, - shots=shots, verbose=True) - - + shots=shots, + verbose=True) def test_send_that_errors_are_caught2(monkeypatch): class MockResponse: def __init__(self, json_data, status_code): pass - + def mocked_requests_post(*args, **kwargs): # Test that this error gets caught raise requests.exceptions.RequestException @@ -324,14 +378,21 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 - json_qasm = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, - 'json': 'instructions','maxCredits': 10,'nq': 1} + json_qasm = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + 'nq': 1 + } name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", token=None, - shots=shots, verbose=True) - + shots=shots, + verbose=True) def test_send_that_errors_are_caught3(monkeypatch): @@ -353,20 +414,33 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 - json_qasm = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, - 'json': 'instructions','maxCredits': 10,'nq': 1} + json_qasm = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + 'nq': 1 + } name = 'projectq_test' _ibm_http_client.send(json_qasm, device="ibmqx4", token=None, - shots=shots, verbose=True) - - + shots=shots, + verbose=True) def test_timeout_exception(monkeypatch): - qasms = {'qasms': [{'qasm': 'my qasm'}],'shots': 1, - 'json': 'instructions','maxCredits': 10,'nq': 1} + qasms = { + 'qasms': [{ + 'qasm': 'my qasm' + }], + 'shots': 1, + 'json': 'instructions', + 'maxCredits': 10, + 'nq': 1 + } json_qasm = qasms tries = [0] @@ -384,12 +458,18 @@ def raise_for_status(self): # Accessing status of device. Return device info. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[1] == urljoin(_api_url, status_url): - connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), - (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': connections, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) - job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format("123e") - if args[1] == urljoin(_api_url, job_url): + if args[1] == urljoin(_API_URL, status_url): + connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return MockResponse([{ + 'backend_name': 'ibmqx4', + 'coupling_map': connections, + 'backend_version': '0.1.547', + 'n_qubits': 32 + }], 200) + job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format( + "123e") + if args[1] == urljoin(_API_URL, job_url): tries[0] += 1 return MockResponse({"status": "RUNNING"}, 200) @@ -410,10 +490,10 @@ def json(self): def raise_for_status(self): pass - jobs_url='Network/ibm-q/Groups/open/Projects/main/Jobs' - if args[1] == _auth_api_url: + jobs_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs' + if args[1] == _AUTH_API_URL: return MockPostResponse({"userId": "1", "id": "12"}) - if args[1] == urljoin(_api_url, jobs_url): + if args[1] == urljoin(_API_URL, jobs_url): return MockPostResponse({"id": "123e"}) monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) @@ -424,7 +504,9 @@ def raise_for_status(self): _ibm_http_client.send(json_qasm, device="ibmqx4", token="test", - shots=1,num_retries=10, verbose=False) + shots=1, + num_retries=10, + verbose=False) assert "123e" in str(excinfo.value) # check that job id is in exception assert tries[0] > 0 @@ -446,14 +528,31 @@ def raise_for_status(self): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[1] == urljoin(_api_url, status_url) and request_num[0] < 2: - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) - elif args[1] == urljoin(_api_url, status_url):#ibmqx4 gets disconnected, replaced by ibmqx5 - return MockResponse([{'backend_name': 'ibmqx5', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) - job_url = job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format("123e") - if args[1] == urljoin(_api_url, job_url): + if args[1] == urljoin(_API_URL, status_url) and request_num[0] < 2: + return MockResponse([{ + 'backend_name': 'ibmqx4', + 'coupling_map': None, + 'backend_version': '0.1.547', + 'n_qubits': 32 + }], 200) + elif args[1] == urljoin( + _API_URL, + status_url): # ibmqx4 gets disconnected, replaced by ibmqx5 + return MockResponse([{ + 'backend_name': 'ibmqx5', + 'coupling_map': None, + 'backend_version': '0.1.547', + 'n_qubits': 32 + }], 200) + job_url = job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format( + "123e") + if args[1] == urljoin(_API_URL, job_url): request_num[0] += 1 - return MockResponse({"status": "RUNNING",'iteration':request_num[0]}, 200) + return MockResponse( + { + "status": "RUNNING", + 'iteration': request_num[0] + }, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -472,7 +571,7 @@ def json(self): def raise_for_status(self): pass - if args[1] == _auth_api_url: + if args[1] == _AUTH_API_URL: return MockPostResponse({"userId": "1", "id": "12"}) monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) @@ -503,15 +602,27 @@ def raise_for_status(self): # Accessing status of device. Return online. status_url = 'Network/ibm-q/Groups/open/Projects/main/devices/v/1' - if args[1] == urljoin(_api_url, status_url): - return MockResponse([{'backend_name': 'ibmqx4', 'coupling_map': None, 'backend_version': '0.1.547', 'n_qubits': 32}], 200) - job_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/{}'.format("123e") - if args[1] == urljoin(_api_url, job_url) and request_num[0] < 1: + if args[1] == urljoin(_API_URL, status_url): + return MockResponse([{ + 'backend_name': 'ibmqx4', + 'coupling_map': None, + 'backend_version': '0.1.547', + 'n_qubits': 32 + }], 200) + job_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/{}'.format( + "123e") + if args[1] == urljoin(_API_URL, job_url) and request_num[0] < 1: request_num[0] += 1 return MockResponse({"status": "RUNNING"}, 200) - elif args[1] == urljoin(_api_url, job_url): - return MockResponse({"qObjectResult": {'qasm': 'qasm', - 'results': ['correct']},"status": "COMPLETED"}, 200) + elif args[1] == urljoin(_API_URL, job_url): + return MockResponse( + { + "qObjectResult": { + 'qasm': 'qasm', + 'results': ['correct'] + }, + "status": "COMPLETED" + }, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -530,7 +641,7 @@ def json(self): def raise_for_status(self): pass - if args[1] == _auth_api_url: + if args[1] == _AUTH_API_URL: return MockPostResponse({"userId": "1", "id": "12"}) monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index 31262d057..f6890d34c 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -11,25 +11,15 @@ # 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.backends._ibm._ibm.py.""" import pytest -import requests -import json import math from projectq.setups import restrictedgateset from projectq import MainEngine from projectq.backends._ibm import _ibm -from projectq.backends._ibm import _ibm_http_client -from projectq.cengines import (TagRemover, - LocalOptimizer, - AutoReplacer, - IBM5QubitMapper, - BasicMapperEngine, - SwapAndCNOTFlipper, - DummyEngine, - DecompositionRuleSet) +from projectq.cengines import (BasicMapperEngine, DummyEngine) + from projectq.ops import (All, Allocate, Barrier, Command, Deallocate, Entangle, Measure, NOT, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z, H, CNOT) @@ -41,27 +31,29 @@ def no_requests(monkeypatch): monkeypatch.delattr("requests.sessions.Session.request") -@pytest.mark.parametrize("single_qubit_gate, is_available", [ - (X, False), (Y, False), (Z, False), (H, True), (T, False), (Tdag, False), (S, False), - (Sdag, False), (Allocate, True), (Deallocate, True), (Measure, True), - (NOT, False), (Rx(0.5), True), (Ry(0.5), True), (Rz(0.5), True), - (Barrier, True), (Entangle, False)]) +@pytest.mark.parametrize("single_qubit_gate, is_available", + [(X, False), (Y, False), (Z, False), (H, True), + (T, False), (Tdag, False), (S, False), (Sdag, False), + (Allocate, True), (Deallocate, True), + (Measure, True), (NOT, False), (Rx(0.5), True), + (Ry(0.5), True), (Rz(0.5), True), (Barrier, True), + (Entangle, False)]) def test_ibm_backend_is_available(single_qubit_gate, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() ibm_backend = _ibm.IBMBackend() - cmd = Command(eng, single_qubit_gate, (qubit1,)) + cmd = Command(eng, single_qubit_gate, (qubit1, )) assert ibm_backend.is_available(cmd) == is_available -@pytest.mark.parametrize("num_ctrl_qubits, is_available", [ - (0, False), (1, True), (2, False), (3, False)]) +@pytest.mark.parametrize("num_ctrl_qubits, is_available", + [(0, False), (1, True), (2, False), (3, False)]) def test_ibm_backend_is_available_control_not(num_ctrl_qubits, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() qureg = eng.allocate_qureg(num_ctrl_qubits) ibm_backend = _ibm.IBMBackend() - cmd = Command(eng, NOT, (qubit1,), controls=qureg) + cmd = Command(eng, NOT, (qubit1, ), controls=qureg) assert ibm_backend.is_available(cmd) == is_available @@ -80,15 +72,15 @@ def test_ibm_sent_error(monkeypatch): # patch send def mock_send(*args, **kwargs): raise TypeError + monkeypatch.setattr(_ibm, "send", mock_send) backend = _ibm.IBMBackend(verbose=True) mapper = BasicMapperEngine() - res=dict() + res = dict() for i in range(4): - res[i]=i + res[i] = i mapper.current_mapping = res - eng = MainEngine(backend=backend, - engine_list=[mapper]) + eng = MainEngine(backend=backend, engine_list=[mapper]) qubit = eng.allocate_qubit() Rx(math.pi) | qubit with pytest.raises(Exception): @@ -99,60 +91,79 @@ def mock_send(*args, **kwargs): dummy.is_last_engine = True eng.next_engine = dummy + def test_ibm_sent_error_2(monkeypatch): backend = _ibm.IBMBackend(verbose=True) mapper = BasicMapperEngine() - res=dict() + res = dict() for i in range(4): - res[i]=i + res[i] = i mapper.current_mapping = res - eng = MainEngine(backend=backend, - engine_list=[mapper]) + eng = MainEngine(backend=backend, engine_list=[mapper]) qubit = eng.allocate_qubit() Rx(math.pi) | qubit with pytest.raises(Exception): - S | qubit # no setup to decompose S gate, so not accepted by the Backend + S | qubit # no setup to decompose S gate, so not accepted by the backend dummy = DummyEngine() dummy.is_last_engine = True eng.next_engine = dummy - - def test_ibm_retrieve(monkeypatch): # patch send def mock_retrieve(*args, **kwargs): - return {'data': {'counts': {'0x0': 504, '0x2': 8, '0xc': 6, '0xe': 482}}, - 'header': {'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]], + return { + 'data': { + 'counts': { + '0x0': 504, + '0x2': 8, + '0xc': 6, + '0xe': 482 + } + }, + 'header': { + 'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]], 'creg_sizes': [['c', 4]], - 'memory_slots': 4, - 'n_qubits': 32, - 'name': 'circuit0', + 'memory_slots': + 4, + 'n_qubits': + 32, + 'name': + 'circuit0', 'qreg_sizes': [['q', 32]], - 'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3], ['q', 4], ['q', 5], - ['q', 6], ['q', 7], ['q', 8], ['q', 9], ['q', 10], ['q', 11], - ['q', 12], ['q', 13], ['q', 14], ['q', 15], ['q', 16], - ['q', 17], ['q', 18], ['q', 19], ['q', 20], ['q', 21], - ['q', 22], ['q', 23], ['q', 24], ['q', 25], ['q', 26], - ['q', 27], ['q', 28], ['q', 29], ['q', 30], ['q', 31]]}, - 'metadata': {'measure_sampling': True, 'method': 'statevector', 'parallel_shots': 1, - 'parallel_state_update': 16}, - 'seed_simulator': 465435780, - 'shots': 1000, - 'status': 'DONE', - 'success': True, - 'time_taken': 0.0045786460000000005} + 'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3], + ['q', 4], ['q', 5], ['q', 6], ['q', 7], + ['q', 8], ['q', 9], ['q', 10], ['q', 11], + ['q', 12], ['q', 13], ['q', 14], ['q', 15], + ['q', 16], ['q', 17], ['q', 18], ['q', 19], + ['q', 20], ['q', 21], ['q', 22], ['q', 23], + ['q', 24], ['q', 25], ['q', 26], ['q', 27], + ['q', 28], ['q', 29], ['q', 30], ['q', 31]] + }, + 'metadata': { + 'measure_sampling': True, + 'method': 'statevector', + 'parallel_shots': 1, + 'parallel_state_update': 16 + }, + 'seed_simulator': 465435780, + 'shots': 1000, + 'status': 'DONE', + 'success': True, + 'time_taken': 0.0045786460000000005 + } + monkeypatch.setattr(_ibm, "retrieve", mock_retrieve) - backend = _ibm.IBMBackend(retrieve_execution="ab1s2",num_runs=1000) + backend = _ibm.IBMBackend(retrieve_execution="ab1s2", num_runs=1000) mapper = BasicMapperEngine() - res=dict() + res = dict() for i in range(4): - res[i]=i + res[i] = i mapper.current_mapping = res - ibm_setup=[mapper] - setup=restrictedgateset.get_engine_list(one_qubit_gates=(Rx,Ry,Rz,H), - two_qubit_gates=(CNOT,)) + ibm_setup = [mapper] + setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry, Rz, H), + two_qubit_gates=(CNOT, )) setup.extend(ibm_setup) eng = MainEngine(backend=backend, engine_list=setup) unused_qubit = eng.allocate_qubit() @@ -175,63 +186,129 @@ def mock_retrieve(*args, **kwargs): def test_ibm_backend_functional_test(monkeypatch): - correct_info = {'json': [{'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793]}, - {'qubits': [1, 2], 'name': 'cx'}, {'qubits': [1, 3], 'name': 'cx'}, - {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, - {'qubits': [1], 'name': 'u1', 'params': [11.780972450962]}, - {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, - {'qubits': [1], 'name': 'u1', 'params': [10.995574287564]}, - {'qubits': [1, 2, 3], 'name': 'barrier'}, - {'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966]}, - {'qubits': [1], 'name': 'measure', 'memory': [1]}, - {'qubits': [2], 'name': 'measure', 'memory': [2]}, - {'qubits': [3], 'name': 'measure', 'memory': [3]}], - 'nq': 4, - 'shots': 1000, - 'maxCredits': 10, - 'backend': {'name': 'ibmq_qasm_simulator'}} - #{'qasms': [{'qasm': '\ninclude "qelib1.inc";\nqreg q[4];\ncreg c[4];\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];'}], 'json': [{'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793]}, {'qubits': [1, 2], 'name': 'cx'}, {'qubits': [1, 3], 'name': 'cx'}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [11.780972450962]}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [10.995574287564]}, {'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966]}, {'qubits': [1], 'name': 'measure', 'memory': [1]}, {'qubits': [2], 'name': 'measure', 'memory': [2]}, {'qubits': [3], 'name': 'measure', 'memory': [3]}], 'nq': 4, 'shots': 1000, 'maxCredits': 10, 'backend': {'name': 'ibmq_qasm_simulator'}} + correct_info = { + 'json': [{ + 'qubits': [1], + 'name': 'u2', + 'params': [0, 3.141592653589793] + }, { + 'qubits': [1, 2], + 'name': 'cx' + }, { + 'qubits': [1, 3], + 'name': 'cx' + }, { + 'qubits': [1], + 'name': 'u3', + 'params': [6.28318530718, 0, 0] + }, { + 'qubits': [1], + 'name': 'u1', + 'params': [11.780972450962] + }, { + 'qubits': [1], + 'name': 'u3', + 'params': [6.28318530718, 0, 0] + }, { + 'qubits': [1], + 'name': 'u1', + 'params': [10.995574287564] + }, { + 'qubits': [1, 2, 3], + 'name': 'barrier' + }, { + 'qubits': [1], + 'name': 'u3', + 'params': [0.2, -1.5707963267948966, 1.5707963267948966] + }, { + 'qubits': [1], + 'name': 'measure', + 'memory': [1] + }, { + 'qubits': [2], + 'name': 'measure', + 'memory': [2] + }, { + 'qubits': [3], + 'name': 'measure', + 'memory': [3] + }], + 'nq': + 4, + 'shots': + 1000, + 'maxCredits': + 10, + 'backend': { + 'name': 'ibmq_qasm_simulator' + } + } + + # {'qasms': [{'qasm': '\ninclude "qelib1.inc";\nqreg q[4];\ncreg c[4];\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];'}], 'json': [{'qubits': [1], 'name': 'u2', 'params': [0, 3.141592653589793]}, {'qubits': [1, 2], 'name': 'cx'}, {'qubits': [1, 3], 'name': 'cx'}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [11.780972450962]}, {'qubits': [1], 'name': 'u3', 'params': [6.28318530718, 0, 0]}, {'qubits': [1], 'name': 'u1', 'params': [10.995574287564]}, {'qubits': [1], 'name': 'u3', 'params': [0.2, -1.5707963267948966, 1.5707963267948966]}, {'qubits': [1], 'name': 'measure', 'memory': [1]}, {'qubits': [2], 'name': 'measure', 'memory': [2]}, {'qubits': [3], 'name': 'measure', 'memory': [3]}], 'nq': 4, 'shots': 1000, 'maxCredits': 10, 'backend': {'name': 'ibmq_qasm_simulator'}} def mock_send(*args, **kwargs): assert args[0] == correct_info - return {'data': {'counts': {'0x0': 504, '0x2': 8, '0xc': 6, '0xe': 482}}, - 'header': {'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]], + return { + 'data': { + 'counts': { + '0x0': 504, + '0x2': 8, + '0xc': 6, + '0xe': 482 + } + }, + 'header': { + 'clbit_labels': [['c', 0], ['c', 1], ['c', 2], ['c', 3]], 'creg_sizes': [['c', 4]], - 'memory_slots': 4, - 'n_qubits': 32, - 'name': 'circuit0', + 'memory_slots': + 4, + 'n_qubits': + 32, + 'name': + 'circuit0', 'qreg_sizes': [['q', 32]], - 'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3], ['q', 4], ['q', 5], - ['q', 6], ['q', 7], ['q', 8], ['q', 9], ['q', 10], ['q', 11], - ['q', 12], ['q', 13], ['q', 14], ['q', 15], ['q', 16], - ['q', 17], ['q', 18], ['q', 19], ['q', 20], ['q', 21], - ['q', 22], ['q', 23], ['q', 24], ['q', 25], ['q', 26], - ['q', 27], ['q', 28], ['q', 29], ['q', 30], ['q', 31]]}, - 'metadata': {'measure_sampling': True, 'method': 'statevector', 'parallel_shots': 1, - 'parallel_state_update': 16}, - 'seed_simulator': 465435780, - 'shots': 1000, - 'status': 'DONE', - 'success': True, - 'time_taken': 0.0045786460000000005} + 'qubit_labels': [['q', 0], ['q', 1], ['q', 2], ['q', 3], + ['q', 4], ['q', 5], ['q', 6], ['q', 7], + ['q', 8], ['q', 9], ['q', 10], ['q', 11], + ['q', 12], ['q', 13], ['q', 14], ['q', 15], + ['q', 16], ['q', 17], ['q', 18], ['q', 19], + ['q', 20], ['q', 21], ['q', 22], ['q', 23], + ['q', 24], ['q', 25], ['q', 26], ['q', 27], + ['q', 28], ['q', 29], ['q', 30], ['q', 31]] + }, + 'metadata': { + 'measure_sampling': True, + 'method': 'statevector', + 'parallel_shots': 1, + 'parallel_state_update': 16 + }, + 'seed_simulator': 465435780, + 'shots': 1000, + 'status': 'DONE', + 'success': True, + 'time_taken': 0.0045786460000000005 + } + monkeypatch.setattr(_ibm, "send", mock_send) - backend = _ibm.IBMBackend(verbose=True,num_runs=1000) + backend = _ibm.IBMBackend(verbose=True, num_runs=1000) import sys # no circuit has been executed -> raises exception with pytest.raises(RuntimeError): backend.get_probabilities([]) mapper = BasicMapperEngine() - res=dict() + res = dict() for i in range(4): - res[i]=i + res[i] = i mapper.current_mapping = res - ibm_setup=[mapper] - setup=restrictedgateset.get_engine_list(one_qubit_gates=(Rx,Ry,Rz,H), - two_qubit_gates=(CNOT,),other_gates=(Barrier,)) + ibm_setup = [mapper] + setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry, Rz, H), + two_qubit_gates=(CNOT, ), + other_gates=(Barrier, )) setup.extend(ibm_setup) eng = MainEngine(backend=backend, engine_list=setup) - #4 qubits circuit is run, but first is unused to test ability for get_probability - #to return the correct values for a subset of the total register + # 4 qubits circuit is run, but first is unused to test ability for + # get_probability to return the correct values for a subset of the total + # register unused_qubit = eng.allocate_qubit() qureg = eng.allocate_qureg(3) # entangle the qureg @@ -248,16 +325,16 @@ def mock_send(*args, **kwargs): prob_dict = eng.backend.get_probabilities([qureg[2], qureg[1]]) assert prob_dict['00'] == pytest.approx(0.512) assert prob_dict['11'] == pytest.approx(0.488) - result="\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];" + result = "\nu2(0,pi/2) q[1];\ncx q[1], q[2];\ncx q[1], q[3];" if sys.version_info.major == 3: - result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];" - result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];" + result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972450962) q[1];" + result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(10.995574287564) q[1];" else: - result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972451) q[1];" - result+="\nu3(6.28318530718, 0, 0) q[1];\nu1(10.9955742876) q[1];" - result+="\nbarrier q[1], q[2], q[3];" - result+="\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];" - result+="\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];" + result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(11.780972451) q[1];" + result += "\nu3(6.28318530718, 0, 0) q[1];\nu1(10.9955742876) q[1];" + result += "\nbarrier q[1], q[2], q[3];" + result += "\nu3(0.2, -pi/2, pi/2) q[1];\nmeasure q[1] -> c[1];" + result += "\nmeasure q[2] -> c[2];\nmeasure q[3] -> c[3];" assert eng.backend.get_qasm() == result diff --git a/projectq/cengines/_ibm5qubitmapper.py b/projectq/cengines/_ibm5qubitmapper.py index d60525a96..2c85749d2 100755 --- a/projectq/cengines/_ibm5qubitmapper.py +++ b/projectq/cengines/_ibm5qubitmapper.py @@ -11,12 +11,9 @@ # 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. - """ Contains a compiler engine to map to the 5-qubit IBM chip """ -from copy import deepcopy - import itertools from projectq.cengines import BasicMapperEngine @@ -39,7 +36,6 @@ class IBM5QubitMapper(BasicMapperEngine): without performing Swaps, the mapping procedure **raises an Exception**. """ - def __init__(self, connections=None): """ Initialize an IBM 5-qubit mapper compiler engine. @@ -49,12 +45,16 @@ def __init__(self, connections=None): BasicMapperEngine.__init__(self) self.current_mapping = dict() self._reset() + self._cmds = [] + self._interactions = dict() + if connections is None: #general connectivity easier for testing functions - self.connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), - (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + self.connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), + (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), + (4, 3)]) else: - self.connections=connections + self.connections = connections def is_available(self, cmd): """ @@ -73,17 +73,6 @@ def _reset(self): self._cmds = [] self._interactions = dict() - def _is_cnot(self, cmd): - """ - Check if the command corresponds to a CNOT (controlled NOT gate). - - Args: - cmd (Command): Command to check whether it is a controlled NOT - gate. - """ - return (isinstance(cmd.gate, NOT.__class__) and - get_control_count(cmd) == 1) - def _determine_cost(self, mapping): """ Determines the cost of the circuit with the given mapping. @@ -96,7 +85,7 @@ def _determine_cost(self, mapping): Cost measure taking into account CNOT directionality or None if the circuit cannot be executed given the mapping. """ - + cost = 0 for tpl in self._interactions: ctrl_id = tpl[0] @@ -120,20 +109,22 @@ def _run(self): the mapping was already determined but more CNOTs get sent down the pipeline. """ - if (len(self.current_mapping) > 0 and - max(self.current_mapping.values()) > 4): + if (len(self.current_mapping) > 0 + and max(self.current_mapping.values()) > 4): raise RuntimeError("Too many qubits allocated. The IBM Q " "device supports at most 5 qubits and no " "intermediate measurements / " "reallocations.") if len(self._interactions) > 0: - logical_ids = [qbid for qbid in self.current_mapping] + logical_ids = list(self.current_mapping) best_mapping = self.current_mapping best_cost = None for physical_ids in itertools.permutations(list(range(5)), len(logical_ids)): - mapping = {logical_ids[i]: physical_ids[i] - for i in range(len(logical_ids))} + mapping = { + logical_ids[i]: physical_ids[i] + for i in range(len(logical_ids)) + } new_cost = self._determine_cost(mapping) if new_cost is not None: if best_cost is None or new_cost < best_cost: @@ -159,7 +150,7 @@ def _store(self, cmd): """ if not cmd.gate == FlushGate(): target = cmd.qubits[0][0].id - if self._is_cnot(cmd): + if _is_cnot(cmd): # CNOT encountered ctrl = cmd.control_qubits[0].id if not (ctrl, target) in self._interactions: @@ -193,3 +184,15 @@ def receive(self, command_list): if isinstance(cmd.gate, FlushGate): self._run() self._reset() + + +def _is_cnot(cmd): + """ + Check if the command corresponds to a CNOT (controlled NOT gate). + + Args: + cmd (Command): Command to check whether it is a controlled NOT + gate. + """ + return (isinstance(cmd.gate, NOT.__class__) + and get_control_count(cmd) == 1) diff --git a/projectq/cengines/_ibm5qubitmapper_test.py b/projectq/cengines/_ibm5qubitmapper_test.py index b92c24ae1..ea6d383b6 100755 --- a/projectq/cengines/_ibm5qubitmapper_test.py +++ b/projectq/cengines/_ibm5qubitmapper_test.py @@ -11,14 +11,13 @@ # 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.cengines._ibm5qubitmapper.py.""" import pytest from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import H, CNOT, X, Measure, All +from projectq.ops import H, CNOT, All from projectq.cengines import _ibm5qubitmapper, SwapAndCNOTFlipper from projectq.backends import IBMBackend @@ -28,6 +27,7 @@ def test_ibm5qubitmapper_is_available(monkeypatch): # Test that IBM5QubitMapper calls IBMBackend if gate is available. def mock_send(*args, **kwargs): return "Yes" + monkeypatch.setattr(_ibm5qubitmapper.IBMBackend, "is_available", mock_send) mapper = _ibm5qubitmapper.IBM5QubitMapper() assert mapper.is_available("TestCommand") == "Yes" @@ -36,8 +36,11 @@ def mock_send(*args, **kwargs): def test_ibm5qubitmapper_invalid_circuit(): connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)]) + eng = MainEngine( + backend=backend, + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) + ]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -54,8 +57,11 @@ def test_ibm5qubitmapper_invalid_circuit(): def test_ibm5qubitmapper_valid_circuit1(): connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)]) + eng = MainEngine( + backend=backend, + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) + ]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -74,8 +80,11 @@ def test_ibm5qubitmapper_valid_circuit1(): def test_ibm5qubitmapper_valid_circuit2(): connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)]) + eng = MainEngine( + backend=backend, + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) + ]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -103,8 +112,11 @@ class FakeIBMBackend(IBMBackend): fake.is_available = backend.is_available backend.is_last_engine = True - eng = MainEngine(backend=fake, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)]) + eng = MainEngine( + backend=fake, + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) + ]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -123,9 +135,12 @@ class FakeIBMBackend(IBMBackend): def test_ibm5qubitmapper_optimizeifpossible(): backend = DummyEngine(save_commands=True) connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) - eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity), - SwapAndCNOTFlipper(connectivity)]) + eng = MainEngine( + backend=backend, + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity), + SwapAndCNOTFlipper(connectivity) + ]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -162,8 +177,10 @@ def test_ibm5qubitmapper_toomanyqubits(): backend = DummyEngine(save_commands=True) connectivity = set([(2, 1), (4, 2), (2, 0), (3, 2), (3, 4), (1, 0)]) eng = MainEngine(backend=backend, - engine_list=[_ibm5qubitmapper.IBM5QubitMapper(), - SwapAndCNOTFlipper(connectivity)]) + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(), + SwapAndCNOTFlipper(connectivity) + ]) qubits = eng.allocate_qureg(6) All(H) | qubits CNOT | (qubits[0], qubits[1]) diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index ded5eafb4..acedeed00 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.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 allowing to compile code for the IBM quantum chips: ->Any 5 qubit devices @@ -27,33 +26,33 @@ import projectq.setups.decompositions from projectq.setups import restrictedgateset from projectq.ops import (Rx, Ry, Rz, H, CNOT, Barrier) -from projectq.cengines import (TagRemover, - LocalOptimizer, - AutoReplacer, - IBM5QubitMapper, - SwapAndCNOTFlipper, - InstructionFilter, - BasicMapperEngine, +from projectq.cengines import (LocalOptimizer, IBM5QubitMapper, + SwapAndCNOTFlipper, BasicMapperEngine, GridMapper) from projectq.backends._ibm._ibm_http_client import show_devices -def get_engine_list(token=None,device=None): + +def get_engine_list(token=None, device=None): # Access to the hardware properties via show_devices # Can also be extended to take into account gate fidelities, new available # gate, etc.. - devices=show_devices(token) - ibm_setup=[] + devices = show_devices(token) + ibm_setup = [] if device not in devices: raise DeviceOfflineError('Error when configuring engine list: device ' 'requested for Backend not connected') - elif devices[device]['nq']==5: + if devices[device]['nq'] == 5: # The requested device is a 5 qubit processor # Obtain the coupling map specific to the device - coupling_map=devices[device]['coupling_map'] - coupling_map=ListToSet(coupling_map) - Mapper=IBM5QubitMapper(coupling_map) - ibm_setup=[Mapper,SwapAndCNOTFlipper(coupling_map),LocalOptimizer(10)] - elif device=='ibmq_qasm_simulator': + coupling_map = devices[device]['coupling_map'] + coupling_map = list2set(coupling_map) + mapper = IBM5QubitMapper(coupling_map) + ibm_setup = [ + mapper, + SwapAndCNOTFlipper(coupling_map), + LocalOptimizer(10) + ] + elif device == 'ibmq_qasm_simulator': # The 32 qubit online simulator doesn't need a specific mapping for # gates. Can also run wider gateset but this setup keep the # restrictedgateset setup for coherence @@ -61,19 +60,40 @@ def get_engine_list(token=None,device=None): # Note: Manual Mapper doesn't work, because its map is updated only if # gates are applied if gates in the register are not used, then it # will lead to state errors - res=dict() + res = dict() for i in range(devices[device]['nq']): - res[i]=i + res[i] = i mapper.current_mapping = res - ibm_setup=[mapper] - elif device=='ibmq_16_melbourne': + ibm_setup = [mapper] + elif device == 'ibmq_16_melbourne': # Only 15 qubits available on this ibmqx2 unit(in particular qubit 7 # on the grid), therefore need custom grid mapping - grid_to_physical = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 15, 8: 14, - 9: 13, 10: 12, 11: 11, 12: 10, 13: 9, 14: 8, 15: 7} - coupling_map=devices[device]['coupling_map'] - coupling_map=ListToSet(coupling_map) - ibm_setup=[GridMapper(2, 8, grid_to_physical),LocalOptimizer(5),SwapAndCNOTFlipper(coupling_map),LocalOptimizer(5)] + grid_to_physical = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 15, + 8: 14, + 9: 13, + 10: 12, + 11: 11, + 12: 10, + 13: 9, + 14: 8, + 15: 7 + } + coupling_map = devices[device]['coupling_map'] + coupling_map = list2set(coupling_map) + ibm_setup = [ + GridMapper(2, 8, grid_to_physical), + LocalOptimizer(5), + SwapAndCNOTFlipper(coupling_map), + LocalOptimizer(5) + ] else: # If there is an online device not handled into ProjectQ it's not too # bad, the engine_list can be constructed manually with the @@ -84,19 +104,23 @@ def get_engine_list(token=None,device=None): # Most gates need to be decomposed into a subset that is manually converted # in the backend (until the implementation of the U1,U2,U3) # available gates decomposable now for U1,U2,U3: Rx,Ry,Rz and H - setup=restrictedgateset.get_engine_list(one_qubit_gates=(Rx,Ry,Rz,H), - two_qubit_gates=(CNOT,),other_gates=(Barrier,)) + setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry, Rz, H), + two_qubit_gates=(CNOT, ), + other_gates=(Barrier, )) setup.extend(ibm_setup) return setup + class DeviceOfflineError(Exception): pass + class DeviceNotHandledError(Exception): pass -def ListToSet(coupling_list): - result=[] + +def list2set(coupling_list): + result = [] for el in coupling_list: result.append(tuple(el)) return set(result) diff --git a/projectq/setups/ibm_test.py b/projectq/setups/ibm_test.py index 9f260528d..26b41b24a 100644 --- a/projectq/setups/ibm_test.py +++ b/projectq/setups/ibm_test.py @@ -13,45 +13,60 @@ # limitations under the License. """Tests for projectq.setup.ibm.""" -import projectq -from projectq import MainEngine -from projectq.cengines import IBM5QubitMapper, SwapAndCNOTFlipper import pytest + def test_ibm_cnot_mapper_in_cengines(monkeypatch): import projectq.setups.ibm + def mock_show_devices(*args, **kwargs): - connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), - (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) - return {'ibmq_burlington':{'coupling_map': connections, - 'version': '0.0.0', 'nq': 5}, - 'ibmq_16_melbourne':{'coupling_map': connections, - 'version': '0.0.0', 'nq': 15}, - 'ibmq_qasm_simulator':{'coupling_map': connections, - 'version': '0.0.0', 'nq': 32} + connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return { + 'ibmq_burlington': { + 'coupling_map': connections, + 'version': '0.0.0', + 'nq': 5 + }, + 'ibmq_16_melbourne': { + 'coupling_map': connections, + 'version': '0.0.0', + 'nq': 15 + }, + 'ibmq_qasm_simulator': { + 'coupling_map': connections, + 'version': '0.0.0', + 'nq': 32 + } } + monkeypatch.setattr(projectq.setups.ibm, "show_devices", mock_show_devices) - engines_5qb=projectq.setups.ibm.get_engine_list(device='ibmq_burlington') - engines_15qb=projectq.setups.ibm.get_engine_list(device='ibmq_16_melbourne') - engines_simulator=projectq.setups.ibm.get_engine_list(device='ibmq_qasm_simulator') - assert len(engines_5qb)==15 - assert len(engines_15qb)==16 - assert len(engines_simulator)==13 + engines_5qb = projectq.setups.ibm.get_engine_list(device='ibmq_burlington') + engines_15qb = projectq.setups.ibm.get_engine_list( + device='ibmq_16_melbourne') + engines_simulator = projectq.setups.ibm.get_engine_list( + device='ibmq_qasm_simulator') + assert len(engines_5qb) == 15 + assert len(engines_15qb) == 16 + assert len(engines_simulator) == 13 - def test_ibm_errors(monkeypatch): import projectq.setups.ibm + def mock_show_devices(*args, **kwargs): - connections=set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), - (2, 1), (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) - return {'ibmq_imaginary':{'coupling_map': connections, - 'version': '0.0.0', 'nq': 6}} + connections = set([(0, 1), (1, 0), (1, 2), (1, 3), (1, 4), (2, 1), + (2, 3), (2, 4), (3, 1), (3, 4), (4, 3)]) + return { + 'ibmq_imaginary': { + 'coupling_map': connections, + 'version': '0.0.0', + 'nq': 6 + } + } + monkeypatch.setattr(projectq.setups.ibm, "show_devices", mock_show_devices) with pytest.raises(projectq.setups.ibm.DeviceOfflineError): - engines_5qb=projectq.setups.ibm.get_engine_list(device='ibmq_burlington') + projectq.setups.ibm.get_engine_list(device='ibmq_burlington') with pytest.raises(projectq.setups.ibm.DeviceNotHandledError): - engines_6qb=projectq.setups.ibm.get_engine_list(device='ibmq_imaginary') - - - + projectq.setups.ibm.get_engine_list(device='ibmq_imaginary') From 2d8e9258c1a3de1657aba38d8d3b197b2b93d87f Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 4 Feb 2020 10:53:35 +0100 Subject: [PATCH 42/43] Improve test coverage --- .../backends/_ibm/_ibm_http_client_test.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/projectq/backends/_ibm/_ibm_http_client_test.py b/projectq/backends/_ibm/_ibm_http_client_test.py index 256e90c41..eb56b1ee4 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -358,6 +358,14 @@ def user_password_input(prompt): shots=shots, verbose=True) + token = '' + with pytest.raises(Exception): + _ibm_http_client.send(json_qasm, + device="ibmqx4", + token=None, + shots=shots, + verbose=True) + def test_send_that_errors_are_caught2(monkeypatch): class MockResponse: @@ -544,8 +552,10 @@ def raise_for_status(self): 'backend_version': '0.1.547', 'n_qubits': 32 }], 200) - job_url = job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format( + job_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format( "123e") + err_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format( + "123ee") if args[1] == urljoin(_API_URL, job_url): request_num[0] += 1 return MockResponse( @@ -553,6 +563,13 @@ def raise_for_status(self): "status": "RUNNING", 'iteration': request_num[0] }, 200) + if args[1] == urljoin(_API_URL, err_url): + request_num[0] += 1 + return MockResponse( + { + "status": "TERMINATED", + 'iteration': request_num[0] + }, 400) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -583,6 +600,11 @@ def raise_for_status(self): token="test", jobid="123e", num_retries=200) + with pytest.raises(Exception): + _ibm_http_client.retrieve(device="ibmqx4", + token="test", + jobid="123ee", + num_retries=200) def test_retrieve(monkeypatch): From eb27bc63313cf0603cdf25136157710f3f738c23 Mon Sep 17 00:00:00 2001 From: Damien Nguyen Date: Tue, 4 Feb 2020 10:59:06 +0100 Subject: [PATCH 43/43] Improve code readability --- projectq/backends/_ibm/_ibm_http_client.py | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 3e6ee9f51..98751bf90 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -127,30 +127,30 @@ def _authenticate(self, token=None): def _run(self, info, device): post_job_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs' shots = info['shots'] - nq = info['nq'] - mq = self.backends[device]['nq'] + n_classical_reg = info['nq'] + n_qubits = self.backends[device]['nq'] version = self.backends[device]['version'] instructions = info['json'] maxcredit = info['maxCredits'] c_label = [] q_label = [] - for i in range(nq): + for i in range(n_classical_reg): c_label.append(['c', i]) - for i in range(mq): + for i in range(n_qubits): q_label.append(['q', i]) experiment = [{ 'header': { - 'qreg_sizes': [['q', mq]], - 'n_qubits': mq, - 'memory_slots': nq, - 'creg_sizes': [['c', nq]], + 'qreg_sizes': [['q', n_qubits]], + 'n_qubits': n_qubits, + 'memory_slots': n_classical_reg, + 'creg_sizes': [['c', n_classical_reg]], 'clbit_labels': c_label, 'qubit_labels': q_label, 'name': 'circuit0' }, 'config': { - 'n_qubits': mq, - 'memory_slots': nq + 'n_qubits': n_qubits, + 'memory_slots': n_classical_reg }, 'instructions': instructions }] @@ -164,8 +164,8 @@ def _run(self, info, device): 'config': { 'shots': shots, 'max_credits': maxcredit, - 'n_qubits': mq, - 'memory_slots': nq, + 'n_qubits': n_qubits, + 'memory_slots': n_classical_reg, 'memory': False, 'parameter_binds': [] },