From ae376fdf9c47b4975b89bdd81f8b8030dc32db0e Mon Sep 17 00:00:00 2001 From: David Bretaud <40793394+dbretaud@users.noreply.github.com> Date: Wed, 23 Sep 2020 17:01:00 +0100 Subject: [PATCH 01/11] Update on the ibmq client (#379) --- projectq/backends/_ibm/_ibm_http_client.py | 37 ++++++++-------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index a6d2ab54a..b2f5c898d 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -129,7 +129,7 @@ def _authenticate(self, token=None): def _run(self, info, device): """ Run the quantum code to the IBMQ machine. - Update since March2020: only protocol available is what they call + Update since September 2020: only protocol available is what they call 'object storage' where a job request via the POST method gets in return a url link to which send the json data. A final http validates the data communication. @@ -162,19 +162,10 @@ def _run(self, info, device): **json_step1) request.raise_for_status() r_json = request.json() - download_endpoint_url = r_json['objectStorageInfo'][ - 'downloadQObjectUrlEndpoint'] - upload_endpoint_url = r_json['objectStorageInfo'][ - 'uploadQobjectUrlEndpoint'] upload_url = r_json['objectStorageInfo']['uploadUrl'] + execution_id = r_json['id'] - # STEP2: WE USE THE ENDPOINT TO GET THE UPLOT LINK - json_step2 = {'allow_redirects': True, 'timeout': (5.0, None)} - request = super(IBMQ, self).get(upload_endpoint_url, **json_step2) - request.raise_for_status() - r_json = request.json() - - # STEP3: WE USE THE ENDPOINT TO GET THE UPLOT LINK + # STEP2: WE UPLOAD THE CIRCUIT DATA n_classical_reg = info['nq'] # hack: easier to restrict labels to measured qubits n_qubits = n_classical_reg # self.backends[device]['nq'] @@ -194,7 +185,7 @@ def _run(self, info, device): data += ('"parameter_binds": [], "memory_slots": ' + str(n_classical_reg)) data += (', "n_qubits": ' + str(n_qubits) - + '}, "schema_version": "1.1.0", ') + + '}, "schema_version": "1.2.0", ') data += '"type": "QASM", "experiments": [{"config": ' data += '{"n_qubits": ' + str(n_qubits) + ', ' data += '"memory_slots": ' + str(n_classical_reg) + '}, ' @@ -205,31 +196,31 @@ def _run(self, info, device): data += '"clbit_labels": ' + str(c_label).replace('\'', '\"') + ', ' data += '"memory_slots": ' + str(n_classical_reg) + ', ' data += '"creg_sizes": [["c", ' + str(n_classical_reg) + ']], ' - data += ('"name": "circuit0"}, "instructions": ' + instruction_str + data += ('"name": "circuit0", "global_phase": 0}, "instructions": ' + instruction_str + '}]}') - json_step3 = { + json_step2 = { 'data': data, 'params': { 'access_token': None }, 'timeout': (5.0, None) } - request = super(IBMQ, self).put(r_json['url'], **json_step3) + request = super(IBMQ, self).put(upload_url, **json_step2) request.raise_for_status() - # STEP4: CONFIRM UPLOAD - json_step4 = { + # STEP3: CONFIRM UPLOAD + json_step3 = { 'data': None, 'json': None, 'timeout': (self.timeout, None) } - upload_data_url = upload_endpoint_url.replace('jobUploadUrl', - 'jobDataUploaded') - request = super(IBMQ, self).post(upload_data_url, **json_step4) + + upload_data_url = urljoin(_API_URL, + 'Network/ibm-q/Groups/open/Projects/main/Jobs/'+str(execution_id) + +'/jobDataUploaded') + request = super(IBMQ, self).post(upload_data_url, **json_step3) request.raise_for_status() - r_json = request.json() - execution_id = upload_endpoint_url.split('/')[-2] return execution_id From c15f3b25dd2efc426507360a19b6b1dded2ec37e Mon Sep 17 00:00:00 2001 From: Bartley Gillan Date: Mon, 14 Dec 2020 10:26:22 -0600 Subject: [PATCH 02/11] Fix install on macOS Big Sur (#383) --- setup.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 5049a3a06..2fb9547d7 100755 --- a/setup.py +++ b/setup.py @@ -370,10 +370,9 @@ def _configure_cxx_standard(self): cxx_standards = [year for year in cxx_standards if year < 17] if sys.platform == 'darwin': - _, minor_version, _ = [ - int(i) for i in platform.mac_ver()[0].split('.') - ] - if minor_version < 14: + major_version = int(platform.mac_ver()[0].split('.')[0]) + minor_version = int(platform.mac_ver()[0].split('.')[1]) + if major_version <= 10 and minor_version < 14: cxx_standards = [year for year in cxx_standards if year < 17] for year in cxx_standards: From dff6adcd8bcfec593448be85ad81e62982db647c Mon Sep 17 00:00:00 2001 From: Peter-Jan Derks <37333222+peter-janderks@users.noreply.github.com> Date: Wed, 17 Feb 2021 10:46:46 +0100 Subject: [PATCH 03/11] Mathgates (#270) * added QuantumAdd __init__ * added test for superposition * quantum addition decomposition added * quantum addition decomposition added * quantum adder working * wrote test for quantum adder * fixed decomposition rule for quantumaddition * added subtract quantum * added comparator * added conditional add * fixed conditional add * added quantum division * fixed division * added comments * additional comments * fixed multiplication algorithm * added hash function testing * pep8 * added complexity information * adding test * add comments in python example code * Update shor.py * Update shor.py * Update _factoring_test.py * Update _constantmath.py * Update _constantmath.py * Update _constantmath.py * Update _constantmath_test.py * Update _constantmath_test.py * Update _constantmath_test.py * Update _constantmath_test.py * Update _constantmath_test.py * Update _quantummath.py * fixed quantum division mathfunction * add test for math functions in _gates.py * file _gates_mathtest.py complete * added tests * control add quantum * quantum_division same as QuantumDivision * added inverse quantum gate * added get_inverse() to subtract quantum * fixed error in inversequantum division * added inverse multiplication * added gate test * Cleanup some tests * Some more rewriting - Renamed all new gate classes to XXXQuantumGate - Create gate instances corresponding to each XXXQuantumGate class - Fixed addition function name in _replace_subtractquantum - Fixed missing get_inverse() method in MultiplyQuantumGate - Updated documentation - Expanded test cases to test both emulation and decomposition of XXXQuantum gates * Fix failing tests for addition and division emulation * Some more cleanup + update year in license headers * Some more cleanup * Remove unneeded function * Some more code cleanup * Revert change by commit a3f572b7ddb23a40d480e54ad36131c9db843e2a - Revert changes made to AddQuantum.get_math_function and _InverseAddQuantum.get_math_function * Minor adjustments to math gate tests * Fix pytest.ini * Adding missing __str__ methods Co-authored-by: Peter-Jan Co-authored-by: Damien Nguyen --- projectq/libs/math/__init__.py | 10 +- projectq/libs/math/_constantmath.py | 14 +- projectq/libs/math/_constantmath_test.py | 49 +-- projectq/libs/math/_default_rules.py | 131 +++++- projectq/libs/math/_gates.py | 348 ++++++++++++++- projectq/libs/math/_gates_math_test.py | 367 ++++++++++++++++ projectq/libs/math/_gates_test.py | 63 ++- projectq/libs/math/_quantummath.py | 525 +++++++++++++++++++++++ projectq/libs/math/_quantummath_test.py | 310 +++++++++++++ pytest.ini | 2 + 10 files changed, 1751 insertions(+), 68 deletions(-) create mode 100644 projectq/libs/math/_gates_math_test.py create mode 100644 projectq/libs/math/_quantummath.py create mode 100644 projectq/libs/math/_quantummath_test.py diff --git a/projectq/libs/math/__init__.py b/projectq/libs/math/__init__.py index 1252fe007..8a0543be2 100755 --- a/projectq/libs/math/__init__.py +++ b/projectq/libs/math/__init__.py @@ -12,9 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ._default_rules import all_defined_decomposition_rules from ._gates import (AddConstant, SubConstant, AddConstantModN, SubConstantModN, - MultiplyByConstantModN) + MultiplyByConstantModN, + AddQuantum, + SubtractQuantum, + ComparatorQuantum, + DivideQuantum, + MultiplyQuantum) + +from ._default_rules import all_defined_decomposition_rules diff --git a/projectq/libs/math/_constantmath.py b/projectq/libs/math/_constantmath.py index 5bc106ba9..2642c0ad3 100755 --- a/projectq/libs/math/_constantmath.py +++ b/projectq/libs/math/_constantmath.py @@ -1,4 +1,4 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# Copyright 2020 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. @@ -15,11 +15,11 @@ import math try: from math import gcd -except ImportError: +except ImportError: # pragma: no cover from fractions import gcd -from projectq.ops import R, X, Swap, Measure, CNOT, QFT -from projectq.meta import Control, Compute, Uncompute, CustomUncompute, Dagger +from projectq.ops import R, X, Swap, CNOT, QFT +from projectq.meta import Control, Compute, Uncompute, CustomUncompute from ._gates import AddConstant, SubConstant, AddConstantModN, SubConstantModN @@ -51,7 +51,7 @@ def add_constant_modN(eng, c, N, quint): using Draper addition and the construction from https://arxiv.org/abs/quant-ph/0205095. """ - assert(c < N and c >= 0) + assert (c < N and c >= 0) AddConstant(c) | quint @@ -84,8 +84,8 @@ def mul_by_constant_modN(eng, c, N, quint_in): (only works if a and N are relative primes, otherwise the modular inverse does not exist). """ - assert(c < N and c >= 0) - assert(gcd(c, N) == 1) + assert (c < N and c >= 0) + assert (gcd(c, N) == 1) n = len(quint_in) quint_out = eng.allocate_qureg(n + 1) diff --git a/projectq/libs/math/_constantmath_test.py b/projectq/libs/math/_constantmath_test.py index 8b0681420..c9abeda15 100755 --- a/projectq/libs/math/_constantmath_test.py +++ b/projectq/libs/math/_constantmath_test.py @@ -1,4 +1,4 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# Copyright 2020 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. @@ -11,14 +11,12 @@ # 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.libs.math_constantmath.py.""" import pytest from projectq import MainEngine -from projectq.cengines import (InstructionFilter, - AutoReplacer, +from projectq.cengines import (InstructionFilter, AutoReplacer, DecompositionRuleSet) from projectq.backends import Simulator from projectq.ops import (All, BasicMathGate, ClassicalInstructionGate, @@ -26,8 +24,7 @@ import projectq.libs.math from projectq.setups.decompositions import qft2crandhadamard, swap2cnot -from projectq.libs.math import (AddConstant, - AddConstantModN, +from projectq.libs.math import (AddConstant, AddConstantModN, MultiplyByConstantModN) @@ -44,72 +41,70 @@ def no_math_emulation(eng, cmd): return True try: return len(cmd.gate.matrix) == 2 - except: + except AttributeError: return False +@pytest.fixture +def eng(): + return MainEngine(backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(no_math_emulation) + ]) + + rule_set = DecompositionRuleSet( modules=[projectq.libs.math, qft2crandhadamard, swap2cnot]) -def test_adder(): - sim = Simulator() - eng = MainEngine(sim, [AutoReplacer(rule_set), - InstructionFilter(no_math_emulation)]) +def test_adder(eng): qureg = eng.allocate_qureg(4) init(eng, qureg, 4) AddConstant(3) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][7])) + assert 1. == pytest.approx(abs(eng.backend.cheat()[1][7])) init(eng, qureg, 7) # reset init(eng, qureg, 2) # check for overflow -> should be 15+2 = 1 (mod 16) AddConstant(15) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][1])) + assert 1. == pytest.approx(abs(eng.backend.cheat()[1][1])) All(Measure) | qureg -def test_modadder(): - sim = Simulator() - eng = MainEngine(sim, [AutoReplacer(rule_set), - InstructionFilter(no_math_emulation)]) - +def test_modadder(eng): qureg = eng.allocate_qureg(4) init(eng, qureg, 4) AddConstantModN(3, 6) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][1])) + assert 1. == pytest.approx(abs(eng.backend.cheat()[1][1])) init(eng, qureg, 1) # reset init(eng, qureg, 7) AddConstantModN(10, 13) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][4])) + assert 1. == pytest.approx(abs(eng.backend.cheat()[1][4])) All(Measure) | qureg -def test_modmultiplier(): - sim = Simulator() - eng = MainEngine(sim, [AutoReplacer(rule_set), - InstructionFilter(no_math_emulation)]) - +def test_modmultiplier(eng): qureg = eng.allocate_qureg(4) init(eng, qureg, 4) MultiplyByConstantModN(3, 7) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][5])) + assert 1. == pytest.approx(abs(eng.backend.cheat()[1][5])) init(eng, qureg, 5) # reset init(eng, qureg, 7) MultiplyByConstantModN(4, 13) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][2])) + assert 1. == pytest.approx(abs(eng.backend.cheat()[1][2])) All(Measure) | qureg diff --git a/projectq/libs/math/_default_rules.py b/projectq/libs/math/_default_rules.py index af0cf979c..b69f35fe0 100755 --- a/projectq/libs/math/_default_rules.py +++ b/projectq/libs/math/_default_rules.py @@ -1,4 +1,4 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# Copyright 2020 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. @@ -11,23 +11,32 @@ # 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 few default replacement rules for Shor's algorithm to work (see Examples). """ -from projectq.meta import Control, Dagger +from projectq.meta import Control from projectq.cengines import DecompositionRule -from ._gates import (AddConstant, - SubConstant, - AddConstantModN, - SubConstantModN, - MultiplyByConstantModN) -from ._constantmath import (add_constant, - add_constant_modN, - mul_by_constant_modN) +from ._gates import (AddConstant, AddConstantModN, MultiplyByConstantModN, + AddQuantum, SubtractQuantum, ComparatorQuantum, + DivideQuantum, MultiplyQuantum) + +from ._gates import (_InverseAddQuantumGate, _InverseDivideQuantumGate, + _InverseMultiplyQuantumGate) + +from ._constantmath import ( + add_constant, + add_constant_modN, + mul_by_constant_modN, +) + +from ._quantummath import ( + add_quantum, subtract_quantum, inverse_add_quantum_carry, comparator, + quantum_conditional_add, quantum_division, inverse_quantum_division, + quantum_conditional_add_carry, quantum_multiplication, + inverse_quantum_multiplication) def _replace_addconstant(cmd): @@ -58,8 +67,108 @@ def _replace_multiplybyconstantmodN(cmd): with Control(eng, cmd.control_qubits): mul_by_constant_modN(eng, c, N, quint) + +def _replace_addquantum(cmd): + eng = cmd.engine + if cmd.control_qubits == []: + quint_a = cmd.qubits[0] + quint_b = cmd.qubits[1] + if len(cmd.qubits) == 3: + c = cmd.qubits[2] + add_quantum(eng, quint_a, quint_b, c) + else: + add_quantum(eng, quint_a, quint_b) + else: + quint_a = cmd.qubits[0] + quint_b = cmd.qubits[1] + if len(cmd.qubits) == 3: + c = cmd.qubits[2] + with Control(eng, cmd.control_qubits): + quantum_conditional_add_carry(eng, quint_a, quint_b, + cmd.control_qubits, c) + else: + with Control(eng, cmd.control_qubits): + quantum_conditional_add(eng, quint_a, quint_b, + cmd.control_qubits) + + +def _replace_inverse_add_quantum(cmd): + eng = cmd.engine + quint_a = cmd.qubits[0] + quint_b = cmd.qubits[1] + + if len(cmd.qubits) == 3: + quint_c = cmd.qubits[2] + with Control(eng, cmd.control_qubits): + inverse_add_quantum_carry(eng, quint_a, [quint_b, quint_c]) + else: + with Control(eng, cmd.control_qubits): + subtract_quantum(eng, quint_a, quint_b) + + +def _replace_comparator(cmd): + eng = cmd.engine + quint_a = cmd.qubits[0] + quint_b = cmd.qubits[1] + c = cmd.qubits[2] + + with Control(eng, cmd.control_qubits): + comparator(eng, quint_a, quint_b, c) + + +def _replace_quantumdivision(cmd): + eng = cmd.engine + quint_a = cmd.qubits[0] + quint_b = cmd.qubits[1] + quint_c = cmd.qubits[2] + + with Control(eng, cmd.control_qubits): + quantum_division(eng, quint_a, quint_b, quint_c) + + +def _replace_inversequantumdivision(cmd): + eng = cmd.engine + quint_a = cmd.qubits[0] + quint_b = cmd.qubits[1] + quint_c = cmd.qubits[2] + + with Control(eng, cmd.control_qubits): + inverse_quantum_division(eng, quint_a, quint_b, quint_c) + + +def _replace_quantummultiplication(cmd): + eng = cmd.engine + quint_a = cmd.qubits[0] + quint_b = cmd.qubits[1] + quint_c = cmd.qubits[2] + + with Control(eng, cmd.control_qubits): + quantum_multiplication(eng, quint_a, quint_b, quint_c) + + +def _replace_inversequantummultiplication(cmd): + eng = cmd.engine + quint_a = cmd.qubits[0] + quint_b = cmd.qubits[1] + quint_c = cmd.qubits[2] + + with Control(eng, cmd.control_qubits): + inverse_quantum_multiplication(eng, quint_a, quint_b, quint_c) + + all_defined_decomposition_rules = [ DecompositionRule(AddConstant, _replace_addconstant), DecompositionRule(AddConstantModN, _replace_addconstmodN), DecompositionRule(MultiplyByConstantModN, _replace_multiplybyconstantmodN), + DecompositionRule(AddQuantum.__class__, _replace_addquantum), + DecompositionRule(_InverseAddQuantumGate, _replace_inverse_add_quantum), + DecompositionRule(SubtractQuantum.__class__, _replace_inverse_add_quantum), + DecompositionRule(ComparatorQuantum.__class__, _replace_comparator), + DecompositionRule(DivideQuantum.__class__, _replace_quantumdivision), + DecompositionRule(_InverseDivideQuantumGate, + _replace_inversequantumdivision), + DecompositionRule(MultiplyQuantum.__class__, + _replace_quantummultiplication), + DecompositionRule(_InverseMultiplyQuantumGate, + _replace_inversequantummultiplication), ] diff --git a/projectq/libs/math/_gates.py b/projectq/libs/math/_gates.py index ef5cade99..d63ae949d 100755 --- a/projectq/libs/math/_gates.py +++ b/projectq/libs/math/_gates.py @@ -1,4 +1,4 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# Copyright 2020 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. @@ -11,6 +11,7 @@ # 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. +"""Math gates for ProjectQ""" from projectq.ops import BasicMathGate @@ -26,6 +27,9 @@ class AddConstant(BasicMathGate): qunum = eng.allocate_qureg(5) # 5-qubit number X | qunum[1] # qunum is now equal to 2 AddConstant(3) | qunum # qunum is now equal to 5 + + Important: if you run with conditional and carry, carry needs to + be a quantum register for the compiler/decomposition to work. """ def __init__(self, a): """ @@ -37,7 +41,7 @@ def __init__(self, a): It also initializes its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ - BasicMathGate.__init__(self, lambda x: ((x + a),)) + BasicMathGate.__init__(self, lambda x: ((x + a), )) self.a = a def get_inverse(self): @@ -110,7 +114,7 @@ def __init__(self, a, N): It also initializes its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ - BasicMathGate.__init__(self, lambda x: ((x + a) % N,)) + BasicMathGate.__init__(self, lambda x: ((x + a) % N, )) self.a = a self.N = N @@ -125,8 +129,8 @@ def get_inverse(self): return SubConstantModN(self.a, self.N) def __eq__(self, other): - return (isinstance(other, AddConstantModN) and self.a == other.a and - self.N == other.N) + return (isinstance(other, AddConstantModN) and self.a == other.a + and self.N == other.N) def __hash__(self): return hash(str(self)) @@ -200,7 +204,7 @@ def __init__(self, a, N): It also initializes its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ - BasicMathGate.__init__(self, lambda x: ((a * x) % N,)) + BasicMathGate.__init__(self, lambda x: ((a * x) % N, )) self.a = a self.N = N @@ -208,11 +212,339 @@ def __str__(self): return "MultiplyByConstantModN({}, {})".format(self.a, self.N) def __eq__(self, other): - return (isinstance(other, MultiplyByConstantModN) and - self.a == other.a and self.N == other.N) + return (isinstance(other, MultiplyByConstantModN) and self.a == other.a + and self.N == other.N) + + def __hash__(self): + return hash(str(self)) + + def __ne__(self, other): + return not self.__eq__(other) + + +class AddQuantumGate(BasicMathGate): + """ + Adds up two quantum numbers represented by quantum registers. + The numbers are stored from low- to high-bit, i.e., qunum[0] is the LSB. + + Example: + .. code-block:: python + + qunum_a = eng.allocate_qureg(5) # 5-qubit number + qunum_b = eng.allocate_qureg(5) # 5-qubit number + carry_bit = eng.allocate_qubit() + + X | qunum_a[2] #qunum_a is now equal to 4 + X | qunum_b[3] #qunum_b is now equal to 8 + AddQuantum | (qunum_a, qunum_b, carry) + # qunum_a remains 4, qunum_b is now 12 and carry_bit is 0 + """ + def __init__(self): + BasicMathGate.__init__(self, None) + + def __str__(self): + return "AddQuantum" + + def __eq__(self, other): + return (isinstance(other, AddQuantumGate)) def __hash__(self): return hash(str(self)) def __ne__(self, other): return not self.__eq__(other) + + def get_math_function(self, qubits): + n = len(qubits[0]) + + def math_fun(a): + a[1] = a[0] + a[1] + if len(bin(a[1])[2:]) > n: + a[1] = a[1] % (2**n) + + if len(a) == 3: + # Flip the last bit of the carry register + a[2] ^= 1 + return (a) + + return math_fun + + def get_inverse(self): + """ + Return the inverse gate (subtraction of the same number a modulo the + same number N). + """ + return _InverseAddQuantumGate() + + +AddQuantum = AddQuantumGate() + + +class _InverseAddQuantumGate(BasicMathGate): + """ + Internal gate glass to support emulation for inverse + addition. + """ + def __init__(self): + BasicMathGate.__init__(self, None) + + def __str__(self): + return "_InverseAddQuantum" + + def get_math_function(self, qubits): + n = len(qubits[1]) + + def math_fun(a): + if len(a) == 3: + # Flip the last bit of the carry register + a[2] ^= 1 + + a[1] -= a[0] + return (a) + + return math_fun + + +class SubtractQuantumGate(BasicMathGate): + """ + Subtract one quantum number represented by a quantum register from + another quantum number represented by a quantum register. + + Example: + .. code-block:: python + + qunum_a = eng.allocate_qureg(5) # 5-qubit number + qunum_b = eng.allocate_qureg(5) # 5-qubit number + X | qunum_a[2] #qunum_a is now equal to 4 + X | qunum_b[3] #qunum_b is now equal to 8 + SubtractQuantum | (qunum_a, qunum_b) + # qunum_a remains 4, qunum_b is now 4 + + """ + def __init__(self): + """ + Initializes the gate to its base class, BasicMathGate, with the + corresponding function, so it can be emulated efficiently. + """ + def subtract(a, b): + return (a, b - a) + + BasicMathGate.__init__(self, subtract) + + def __str__(self): + return "SubtractQuantum" + + def __eq__(self, other): + return (isinstance(other, SubtractQuantumGate)) + + def __hash__(self): + return hash(str(self)) + + def __ne__(self, other): + return not self.__eq__(other) + + def get_inverse(self): + """ + Return the inverse gate (subtraction of the same number a modulo the + same number N). + """ + return AddQuantum + + +SubtractQuantum = SubtractQuantumGate() + + +class ComparatorQuantumGate(BasicMathGate): + """ + Flips a compare qubit if the binary value of first imput is higher than + the second input. + The numbers are stored from low- to high-bit, i.e., qunum[0] is the LSB. + Example: + .. code-block:: python + + qunum_a = eng.allocate_qureg(5) # 5-qubit number + qunum_b = eng.allocate_qureg(5) # 5-qubit number + compare_bit = eng.allocate_qubit() + X | qunum_a[4] #qunum_a is now equal to 16 + X | qunum_b[3] #qunum_b is now equal to 8 + ComparatorQuantum | (qunum_a, qunum_b, compare_bit) + # qunum_a and qunum_b remain 16 and 8, qunum_b is now 12 and + compare bit is now 1 + + """ + def __init__(self): + """ + Initializes the gate and its base class, BasicMathGate, with the + corresponding function, so it can be emulated efficiently. + """ + def compare(a, b, c): + if b < a: + if c == 0: + c = 1 + else: + c = 0 + return (a, b, c) + + BasicMathGate.__init__(self, compare) + + def __str__(self): + return "Comparator" + + def __eq__(self, other): + return (isinstance(other, ComparatorQuantumGate)) + + def __hash__(self): + return hash(str(self)) + + def __ne__(self, other): + return not self.__eq__(other) + + def get_inverse(self): + """ + Return the inverse gate + """ + return AddQuantum + + +ComparatorQuantum = ComparatorQuantumGate() + + +class DivideQuantumGate(BasicMathGate): + """ + Divides one quantum number from another. Takes three inputs which should + be quantum registers of equal size; a dividend, a remainder and a + divisor. The remainder should be in the state |0...0> and the dividend + should be bigger than the divisor.The gate returns (in this order): the + remainder, the quotient and the divisor. + + The numbers are stored from low- to high-bit, i.e., qunum[0] is the LSB. + + Example: + .. code-block:: python + + qunum_a = eng.allocate_qureg(5) # 5-qubit number + qunum_b = eng.allocate_qureg(5) # 5-qubit number + qunum_c = eng.allocate_qureg(5) # 5-qubit number + + All(X) | [qunum_a[0],qunum_a[3]] #qunum_a is now equal to 9 + X | qunum_c[2] #qunum_c is now equal to 4 + + DivideQuantum | (qunum_a, qunum_b,qunum_c) + # qunum_a is now equal to 1 (remainder), qunum_b is now + # equal to 2 (quotient) and qunum_c remains 4 (divisor) + + |dividend>|remainder>|divisor> -> |remainder>|quotient>|divisor> + """ + def __init__(self): + """ + Initializes the gate and its base class, BasicMathGate, with the + corresponding function, so it can be emulated efficiently. + """ + def division(dividend, remainder, divisor): + if divisor == 0 or divisor > dividend: + return (remainder, dividend, divisor) + + else: + quotient = remainder + dividend // divisor + return ((dividend - (quotient * divisor)), quotient, divisor) + + BasicMathGate.__init__(self, division) + + def get_inverse(self): + return _InverseDivideQuantumGate() + + def __str__(self): + return "DivideQuantum" + + def __eq__(self, other): + return (isinstance(other, DivideQuantumGate)) + + def __hash__(self): + return hash(str(self)) + + def __ne__(self, other): + return not self.__eq__(other) + + +DivideQuantum = DivideQuantumGate() + + +class _InverseDivideQuantumGate(BasicMathGate): + """ + Internal gate glass to support emulation for inverse + division. + """ + def __init__(self): + def inverse_division(remainder, quotient, divisor): + if divisor == 0: + return (quotient, remainder, divisor) + + dividend = remainder + quotient * divisor + remainder = 0 + return (dividend, remainder, divisor) + + BasicMathGate.__init__(self, inverse_division) + + def __str__(self): + return "_InverseDivideQuantum" + +class MultiplyQuantumGate(BasicMathGate): + """ + Multiplies two quantum numbers represented by a quantum registers. + Requires three quantum registers as inputs, the first two are the + numbers to be multiplied and should have the same size (n qubits). The + third register will hold the product and should be of size 2n+1. + The numbers are stored from low- to high-bit, i.e., qunum[0] is the LSB. + + Example: + .. code-block:: python + qunum_a = eng.allocate_qureg(4) + qunum_b = eng.allocate_qureg(4) + qunum_c = eng.allocate_qureg(9) + X | qunum_a[2] # qunum_a is now 4 + X | qunum_b[3] # qunum_b is now 8 + MultiplyQuantum() | (qunum_a, qunum_b, qunum_c) + # qunum_a remains 4 and qunum_b remains 8, qunum_c is now equal to 32 + """ + def __init__(self): + """ + Initializes the gate and its base class, BasicMathGate, with the + corresponding function, so it can be emulated efficiently. + """ + def multiply(a, b, c): + return (a, b, c + a * b) + + BasicMathGate.__init__(self, multiply) + + def __str__(self): + return "MultiplyQuantum" + + def __eq__(self, other): + return (isinstance(other, MultiplyQuantumGate)) + + def __hash__(self): + return hash(str(self)) + + def __ne__(self, other): + return not self.__eq__(other) + + def get_inverse(self): + return _InverseMultiplyQuantumGate() + + +MultiplyQuantum = MultiplyQuantumGate() + + +class _InverseMultiplyQuantumGate(BasicMathGate): + """ + Internal gate glass to support emulation for inverse + multiplication. + """ + def __init__(self): + def inverse_multiplication(a, b, c): + return (a, b, c - a * b) + + BasicMathGate.__init__(self, inverse_multiplication) + + def __str__(self): + return "_InverseMultiplyQuantum" diff --git a/projectq/libs/math/_gates_math_test.py b/projectq/libs/math/_gates_math_test.py new file mode 100644 index 000000000..2717c5927 --- /dev/null +++ b/projectq/libs/math/_gates_math_test.py @@ -0,0 +1,367 @@ +# Copyright 2020 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.libs.math._gates.py.""" + +import pytest + +from projectq.cengines import (MainEngine, TagRemover, AutoReplacer, + InstructionFilter, DecompositionRuleSet) +from projectq.meta import Control, Compute, Uncompute +from projectq.ops import (All, Measure, X, BasicMathGate, + ClassicalInstructionGate) +import projectq.setups.decompositions + +import projectq.libs.math +from . import (AddConstant, AddQuantum, SubtractQuantum, ComparatorQuantum, + DivideQuantum, MultiplyQuantum) + +from projectq.backends import CommandPrinter + + +def print_all_probabilities(eng, qureg): + i = 0 + y = len(qureg) + while i < (2**y): + qubit_list = [int(x) for x in list(('{0:0b}'.format(i)).zfill(y))] + qubit_list = qubit_list[::-1] + prob = eng.backend.get_probability(qubit_list, qureg) + if prob != 0.0: + print(prob, qubit_list, i) + + i += 1 + + +def _eng_emulation(): + # Only decomposing native ProjectQ gates + # -> using emulation for gates in projectq.libs.math + rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) + eng = MainEngine(engine_list=[ + TagRemover(), + AutoReplacer(rule_set), + TagRemover(), + CommandPrinter(), + ], + verbose=True) + return eng + + +def _eng_decomp(): + def no_math_emulation(eng, cmd): + if isinstance(cmd.gate, BasicMathGate): + return False + if isinstance(cmd.gate, ClassicalInstructionGate): + return True + try: + return len(cmd.gate.matrix) > 0 + except AttributeError: + return False + + rule_set = DecompositionRuleSet(modules=[ + projectq.libs.math, projectq.setups.decompositions.qft2crandhadamard + ]) + eng = MainEngine(engine_list=[ + TagRemover(), + AutoReplacer(rule_set), + InstructionFilter(no_math_emulation), + TagRemover(), + CommandPrinter() + ]) + return eng + + +@pytest.fixture(params=['no_decomp', 'full_decomp']) +def eng(request): + if request.param == 'no_decomp': + return _eng_emulation() + elif request.param == 'full_decomp': + return _eng_decomp() + + +def test_constant_addition(eng): + qunum_a = eng.allocate_qureg(5) + X | qunum_a[2] + with Compute(eng): + AddConstant(5) | (qunum_a) + + Uncompute(eng) + + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + + +def test_addition(eng): + qunum_a = eng.allocate_qureg(5) # 5-qubit number + qunum_b = eng.allocate_qureg(5) # 5-qubit number + carry_bit = eng.allocate_qubit() + X | qunum_a[2] # qunum_a is now equal to 4 + X | qunum_b[3] # qunum_b is now equal to 8 + AddQuantum | (qunum_a, qunum_b, carry_bit) + + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 1, 1, 0], qunum_b)) + assert 1. == pytest.approx(eng.backend.get_probability([0], carry_bit)) + + +def test_inverse_addition(eng): + qunum_a = eng.allocate_qureg(5) + qunum_b = eng.allocate_qureg(5) + X | qunum_a[2] + X | qunum_b[3] + with Compute(eng): + AddQuantum | (qunum_a, qunum_b) + Uncompute(eng) + + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) + + +def test_inverse_addition_with_control(eng): + qunum_a = eng.allocate_qureg(5) + qunum_b = eng.allocate_qureg(5) + qunum_c = eng.allocate_qubit() + All(X) | qunum_a + All(X) | qunum_b + X | qunum_c + with Compute(eng): + with Control(eng, qunum_c): + AddQuantum | (qunum_a, qunum_b) + + Uncompute(eng) + + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 1, 1, 1], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 1, 1, 1], qunum_b)) + + +def test_addition_with_control(eng): + qunum_a = eng.allocate_qureg(5) + qunum_b = eng.allocate_qureg(5) + control_bit = eng.allocate_qubit() + X | qunum_a[1] # qunum_a is now equal to 2 + X | qunum_b[4] # qunum_b is now equal to 16 + X | control_bit + with Control(eng, control_bit): + AddQuantum | (qunum_a, qunum_b) + + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 0, 0, 0], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 0, 0, 1], qunum_b)) + + +def test_addition_with_control_carry(eng): + qunum_a = eng.allocate_qureg(4) # 4-qubit number + qunum_b = eng.allocate_qureg(4) # 4-qubit number + control_bit = eng.allocate_qubit() + qunum_c = eng.allocate_qureg(2) + + X | qunum_a[1] # qunum is now equal to 2 + All(X) | qunum_b[0:4] # qunum is now equal to 15 + X | control_bit + + with Control(eng, control_bit): + AddQuantum | (qunum_a, qunum_b, qunum_c) + # qunum_a and ctrl don't change, qunum_b and qunum_c are now both equal + # to 1 so in binary together 10001 (2 + 15 = 17) + + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 0, 0], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 0, 0], qunum_b)) + assert 1. == pytest.approx(eng.backend.get_probability([1], control_bit)) + assert 1. == pytest.approx(eng.backend.get_probability([1, 0], qunum_c)) + + All(Measure) | qunum_a + All(Measure) | qunum_b + + +def test_inverse_addition_with_control_carry(eng): + qunum_a = eng.allocate_qureg(4) + qunum_b = eng.allocate_qureg(4) + + control_bit = eng.allocate_qubit() + qunum_c = eng.allocate_qureg(2) + + X | qunum_a[1] + All(X) | qunum_b[0:4] + X | control_bit + with Compute(eng): + with Control(eng, control_bit): + AddQuantum | (qunum_a, qunum_b, qunum_c) + Uncompute(eng) + + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 0, 0], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 1, 1], qunum_b)) + assert 1. == pytest.approx(eng.backend.get_probability([1], control_bit)) + assert 1. == pytest.approx(eng.backend.get_probability([0, 0], qunum_c)) + + All(Measure) | qunum_a + All(Measure) | qunum_b + Measure | control_bit + All(Measure) | qunum_c + + +def test_subtraction(eng): + qunum_a = eng.allocate_qureg(5) + qunum_b = eng.allocate_qureg(5) + + X | qunum_a[2] + X | qunum_b[3] + + SubtractQuantum | (qunum_a, qunum_b) + + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 1, 0, 0], qunum_b)) + + +def test_inverse_subtraction(eng): + qunum_a = eng.allocate_qureg(5) + qunum_b = eng.allocate_qureg(5) + + X | qunum_a[2] + X | qunum_b[3] + + with Compute(eng): + SubtractQuantum | (qunum_a, qunum_b) + Uncompute(eng) + + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) + + +def test_comparator(eng): + qunum_a = eng.allocate_qureg(5) # 5-qubit number + qunum_b = eng.allocate_qureg(5) # 5-qubit number + compare_bit = eng.allocate_qubit() + X | qunum_a[4] # qunum_a is now equal to 16 + X | qunum_b[3] # qunum_b is now equal to 8 + + ComparatorQuantum | (qunum_a, qunum_b, compare_bit) + + eng.flush() + print_all_probabilities(eng, qunum_a) + print_all_probabilities(eng, qunum_b) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 0, 1], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) + assert 1. == pytest.approx(eng.backend.get_probability([1], compare_bit)) + + +def test_division(eng): + qunum_a = eng.allocate_qureg(5) + qunum_b = eng.allocate_qureg(5) + qunum_c = eng.allocate_qureg(5) + + All(X) | [qunum_a[0], qunum_a[3]] # qunum_a is now equal to 9 + X | qunum_c[2] # qunum_c is now 4 + + DivideQuantum | (qunum_a, qunum_b, qunum_c) + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 0, 0, 0], qunum_a)) # remainder + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 0, 0, 0], qunum_b)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 1, 0, 0], qunum_c)) + + +def test_inverse_division(eng): + qunum_a = eng.allocate_qureg(5) + qunum_b = eng.allocate_qureg(5) + qunum_c = eng.allocate_qureg(5) + + All(X) | [qunum_a[0], qunum_a[3]] + X | qunum_c[2] + + with Compute(eng): + DivideQuantum | (qunum_a, qunum_b, qunum_c) + Uncompute(eng) + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 0, 1, 0], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 0, 0], qunum_b)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 1, 0, 0], qunum_c)) + + +def test_multiplication(eng): + qunum_a = eng.allocate_qureg(4) + qunum_b = eng.allocate_qureg(4) + qunum_c = eng.allocate_qureg(9) + X | qunum_a[2] # qunum_a is now 4 + X | qunum_b[3] # qunum_b is now 8 + MultiplyQuantum | (qunum_a, qunum_b, qunum_c) + # qunum_a remains 4 and qunum_b remains 8, qunum_c is now equal to 32 + + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 0, 0, 1, 0, 0, 0], qunum_c)) + + +def test_inverse_multiplication(eng): + qunum_a = eng.allocate_qureg(4) + qunum_b = eng.allocate_qureg(4) + qunum_c = eng.allocate_qureg(9) + X | qunum_a[2] # qunum_a is now 4 + X | qunum_b[3] # qunum_b is now 8 + with Compute(eng): + MultiplyQuantum | (qunum_a, qunum_b, qunum_c) + Uncompute(eng) + + eng.flush() + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 0, 0, 0, 0, 0, 0], qunum_c)) diff --git a/projectq/libs/math/_gates_test.py b/projectq/libs/math/_gates_test.py index 52852101a..116336204 100755 --- a/projectq/libs/math/_gates_test.py +++ b/projectq/libs/math/_gates_test.py @@ -1,4 +1,4 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# Copyright 2020 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. @@ -11,23 +11,22 @@ # 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.libs.math._gates.py.""" -"""Tests for projectq.libs.math_gates.py.""" +from projectq.libs.math import (AddConstant, AddConstantModN, + MultiplyByConstantModN, SubConstant, + SubConstantModN, AddQuantum, SubtractQuantum, + ComparatorQuantum, DivideQuantum, + MultiplyQuantum) -import pytest - -from projectq.libs.math import (AddConstant, - AddConstantModN, - MultiplyByConstantModN, - SubConstant, - SubConstantModN) +from ._gates import (AddQuantumGate, SubtractQuantumGate, MultiplyQuantumGate, + DivideQuantumGate, ComparatorQuantumGate) def test_addconstant(): assert AddConstant(3) == AddConstant(3) assert not AddConstant(3) == AddConstant(4) assert AddConstant(7) != AddConstant(3) - assert str(AddConstant(3)) == "AddConstant(3)" @@ -37,7 +36,6 @@ def test_addconstantmodn(): assert not AddConstantModN(3, 5) == AddConstantModN(3, 4) assert AddConstantModN(7, 4) != AddConstantModN(3, 4) assert AddConstantModN(3, 5) != AddConstantModN(3, 4) - assert str(AddConstantModN(3, 4)) == "AddConstantModN(3, 4)" @@ -47,14 +45,53 @@ def test_multiplybyconstmodn(): assert not MultiplyByConstantModN(3, 5) == MultiplyByConstantModN(3, 4) assert MultiplyByConstantModN(7, 4) != MultiplyByConstantModN(3, 4) assert MultiplyByConstantModN(3, 5) != MultiplyByConstantModN(3, 4) - assert str(MultiplyByConstantModN(3, 4)) == "MultiplyByConstantModN(3, 4)" +def test_AddQuantum(): + assert AddQuantum == AddQuantumGate() + assert AddQuantum != SubtractQuantum + assert not AddQuantum == SubtractQuantum + assert str(AddQuantum) == "AddQuantum" + + +def test_SubtractQuantum(): + assert SubtractQuantum == SubtractQuantumGate() + assert SubtractQuantum != AddQuantum + assert not SubtractQuantum == ComparatorQuantum + assert str(SubtractQuantum) == "SubtractQuantum" + + +def test_Comparator(): + assert ComparatorQuantum == ComparatorQuantumGate() + assert ComparatorQuantum != AddQuantum + assert not ComparatorQuantum == AddQuantum + assert str(ComparatorQuantum) == "Comparator" + + +def test_QuantumDivision(): + assert DivideQuantum == DivideQuantumGate() + assert DivideQuantum != MultiplyQuantum + assert not DivideQuantum == MultiplyQuantum + assert str(DivideQuantum) == "DivideQuantum" + + +def test_QuantumMultiplication(): + assert MultiplyQuantum == MultiplyQuantumGate() + assert MultiplyQuantum != DivideQuantum + assert not MultiplyQuantum == DivideQuantum + assert str(MultiplyQuantum) == "MultiplyQuantum" + + def test_hash_function_implemented(): assert hash(AddConstant(3)) == hash(str(AddConstant(3))) assert hash(SubConstant(-3)) == hash(str(AddConstant(3))) assert hash(AddConstantModN(7, 4)) == hash(str(AddConstantModN(7, 4))) assert hash(SubConstantModN(7, 4)) == hash(str(AddConstantModN(-3, 4))) assert hash(MultiplyByConstantModN(3, 5)) == hash( - MultiplyByConstantModN(3, 5)) + str(MultiplyByConstantModN(3, 5))) + assert hash(AddQuantum) == hash(str(AddQuantum)) + assert hash(SubtractQuantum) == hash(str(SubtractQuantum)) + assert hash(ComparatorQuantum) == hash(str(ComparatorQuantum)) + assert hash(DivideQuantum) == hash(str(DivideQuantum)) + assert hash(MultiplyQuantum) == hash(str(MultiplyQuantum)) diff --git a/projectq/libs/math/_quantummath.py b/projectq/libs/math/_quantummath.py new file mode 100644 index 000000000..5c4d5ed5b --- /dev/null +++ b/projectq/libs/math/_quantummath.py @@ -0,0 +1,525 @@ +# Copyright 2020 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. + +from projectq.ops import All, X, CNOT +from projectq.meta import Control +from ._gates import (AddQuantum, SubtractQuantum) + + +def add_quantum(eng, quint_a, quint_b, carry=None): + """ + Adds two quantum integers, i.e., + + |a0...a(n-1)>|b(0)...b(n-1)>|c> -> |a0...a(n-1)>|b+a(0)...b+a(n)> + + (only works if quint_a and quint_b are the same size and carry is a single + qubit) + + Args: + eng (MainEngine): ProjectQ MainEngine + quint_a (list): Quantum register (or list of qubits) + quint_b (list): Quantum register (or list of qubits) + carry (list): Carry qubit + + Notes: + Ancilla: 0, size: 7n-6, toffoli: 2n-1, depth: 5n-3. + + .. rubric:: References + + Quantum addition using ripple carry from: + https://arxiv.org/pdf/0910.2530.pdf + """ + # pylint: disable = pointless-statement + + assert len(quint_a) == len(quint_b) + assert carry and len(carry) == 1 or not carry + + n = len(quint_a) + 1 + + for i in range(1, n - 1): + CNOT | (quint_a[i], quint_b[i]) + + if carry: + CNOT | (quint_a[n - 2], carry) + + for j in range(n - 3, 0, -1): + CNOT | (quint_a[j], quint_a[j + 1]) + + for k in range(0, n - 2): + with Control(eng, [quint_a[k], quint_b[k]]): + X | (quint_a[k + 1]) + + if carry: + with Control(eng, [quint_a[n - 2], quint_b[n - 2]]): + X | carry + + for l in range(n - 2, 0, -1): + CNOT | (quint_a[l], quint_b[l]) + with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): + X | quint_a[l] + + for m in range(1, n - 2): + CNOT | (quint_a[m], quint_a[m + 1]) + + for n in range(0, n - 1): + CNOT | (quint_a[n], quint_b[n]) + + +def subtract_quantum(eng, quint_a, quint_b): + """ + Subtracts two quantum integers, i.e., + + |a>|b> -> |a>|b-a> + + (only works if quint_a and quint_b are the same size) + + Args: + eng (MainEngine): ProjectQ MainEngine + quint_a (list): Quantum register (or list of qubits) + quint_b (list): Quantum register (or list of qubits) + + Notes: + Quantum subtraction using bitwise complementation of quantum + adder: b-a = (a + b')'. Same as the quantum addition circuit + except that the steps involving the carry qubit are left out + and complement b at the start and at the end of the circuit is + added. + + Ancilla: 0, size: 9n-8, toffoli: 2n-2, depth: 5n-5. + + + .. rubric:: References + + Quantum addition using ripple carry from: + https://arxiv.org/pdf/0910.2530.pdf + """ + # pylint: disable = pointless-statement, expression-not-assigned + + assert len(quint_a) == len(quint_b) + n = len(quint_a) + 1 + + All(X) | quint_b + + for i in range(1, n - 1): + CNOT | (quint_a[i], quint_b[i]) + + for j in range(n - 3, 0, -1): + CNOT | (quint_a[j], quint_a[j + 1]) + + for k in range(0, n - 2): + with Control(eng, [quint_a[k], quint_b[k]]): + X | (quint_a[k + 1]) + + for l in range(n - 2, 0, -1): + CNOT | (quint_a[l], quint_b[l]) + with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): + X | quint_a[l] + + for m in range(1, n - 2): + CNOT | (quint_a[m], quint_a[m + 1]) + + for n in range(0, n - 1): + CNOT | (quint_a[n], quint_b[n]) + + All(X) | quint_b + + +def inverse_add_quantum_carry(eng, quint_a, quint_b): + """ + Inverse of quantum addition with carry + + Args: + eng (MainEngine): ProjectQ MainEngine + quint_a (list): Quantum register (or list of qubits) + quint_b (list): Quantum register (or list of qubits) + """ + # pylint: disable = pointless-statement, expression-not-assigned + # pylint: disable = unused-argument + + assert (len(quint_a) == len(quint_b[0])) + + All(X) | quint_b[0] + X | quint_b[1][0] + + AddQuantum | (quint_a, quint_b[0], quint_b[1]) + + All(X) | quint_b[0] + X | quint_b[1][0] + + +def comparator(eng, quint_a, quint_b, comp): + """ + Compares the size of two quantum integers, i.e, + + if a>b: |a>|b>|c> -> |a>|b>|c+1> + + else: |a>|b>|c> -> |a>|b>|c> + + (only works if quint_a and quint_b are the same size and the comparator + is 1 qubit) + + Args: + eng (MainEngine): ProjectQ MainEngine + quint_a (list): Quantum register (or list of qubits) + quint_b (list): Quantum register (or list of qubits) + comp (Qubit): Comparator qubit + + Notes: + Comparator flipping a compare qubit by computing the high bit + of b-a, which is 1 if and only if a > b. The high bit is + computed using the first half of circuit in AddQuantum (such + that the high bit is written to the carry qubit) and then + undoing the first half of the circuit. By complementing b at + the start and b+a at the end the high bit of b-a is + calculated. + + Ancilla: 0, size: 8n-3, toffoli: 2n+1, depth: 4n+3. + """ + # pylint: disable = pointless-statement, expression-not-assigned + + assert len(quint_a) == len(quint_b) + assert len(comp) == 1 + + n = len(quint_a) + 1 + + All(X) | quint_b + + for i in range(1, n - 1): + CNOT | (quint_a[i], quint_b[i]) + + CNOT | (quint_a[n - 2], comp) + + for j in range(n - 3, 0, -1): + CNOT | (quint_a[j], quint_a[j + 1]) + + for k in range(0, n - 2): + with Control(eng, [quint_a[k], quint_b[k]]): + X | (quint_a[k + 1]) + + with Control(eng, [quint_a[n - 2], quint_b[n - 2]]): + X | comp + + for k in range(0, n - 2): + with Control(eng, [quint_a[k], quint_b[k]]): + X | (quint_a[k + 1]) + + for j in range(n - 3, 0, -1): + CNOT | (quint_a[j], quint_a[j + 1]) + + for i in range(1, n - 1): + CNOT | (quint_a[i], quint_b[i]) + + All(X) | quint_b + + +def quantum_conditional_add(eng, quint_a, quint_b, conditional): + """ + Adds up two quantum integers if conditional is high, i.e., + + |a>|b>|c> -> |a>|b+a>|c> + (without a carry out qubit) + + if conditional is low, no operation is performed, i.e., + |a>|b>|c> -> |a>|b>|c> + + Args: + eng (MainEngine): ProjectQ MainEngine + quint_a (list): Quantum register (or list of qubits) + quint_b (list): Quantum register (or list of qubits) + conditional (list): Conditional qubit + + Notes: + Ancilla: 0, Size: 7n-7, Toffoli: 3n-3, Depth: 5n-3. + + .. rubric:: References + + Quantum Conditional Add from https://arxiv.org/pdf/1609.01241.pdf + """ + # pylint: disable = pointless-statement, expression-not-assigned + + assert len(quint_a) == len(quint_b) + assert len(conditional) == 1 + + n = len(quint_a) + 1 + + for i in range(1, n - 1): + CNOT | (quint_a[i], quint_b[i]) + + for i in range(n - 2, 1, -1): + CNOT | (quint_a[i - 1], quint_a[i]) + + for k in range(0, n - 2): + with Control(eng, [quint_a[k], quint_b[k]]): + X | (quint_a[k + 1]) + + with Control(eng, [quint_a[n - 2], conditional[0]]): + X | quint_b[n - 2] + + for l in range(n - 2, 0, -1): + with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): + X | quint_a[l] + with Control(eng, [quint_a[l - 1], conditional[0]]): + X | (quint_b[l - 1]) + + for m in range(1, n - 2): + CNOT | (quint_a[m], quint_a[m + 1]) + + for o in range(1, n - 1): + CNOT | (quint_a[o], quint_b[o]) + + +def quantum_division(eng, dividend, remainder, divisor): + """ + Performs restoring integer division, i.e., + + |dividend>|remainder>|divisor> -> |remainder>|quotient>|divisor> + + (only works if all three qubits are of equal length) + + Args: + eng (MainEngine): ProjectQ MainEngine + dividend (list): Quantum register (or list of qubits) + remainder (list): Quantum register (or list of qubits) + divisor (list): Quantum register (or list of qubits) + + Notes: + Ancilla: n, size 16n^2 - 13, toffoli: 5n^2 -5 , depth: 10n^2-6. + + .. rubric:: References + + Quantum Restoring Integer Division from: + https://arxiv.org/pdf/1609.01241.pdf. + """ + # pylint: disable = pointless-statement, expression-not-assigned + + # The circuit consits of three parts + # i) leftshift + # ii) subtraction + # iii) conditional add operation. + + assert len(dividend) == len(remainder) == len(divisor) + + j = len(remainder) + n = len(dividend) + + while j != 0: + combined_reg = [] + + combined_reg.append(dividend[n - 1]) + + for i in range(0, n - 1): + combined_reg.append(remainder[i]) + + SubtractQuantum | (divisor[0:n], combined_reg) + CNOT | (combined_reg[n - 1], remainder[n - 1]) + with Control(eng, remainder[n - 1]): + AddQuantum | (divisor[0:n], combined_reg) + X | remainder[n - 1] + + remainder.insert(0, dividend[n - 1]) + dividend.insert(0, remainder[n]) + del remainder[n] + del dividend[n] + + j -= 1 + + +def inverse_quantum_division(eng, remainder, quotient, divisor): + """ + Performs the inverse of a restoring integer division, i.e., + + |remainder>|quotient>|divisor> -> |dividend>|remainder(0)>|divisor> + + Args: + eng (MainEngine): ProjectQ MainEngine + dividend (list): Quantum register (or list of qubits) + remainder (list): Quantum register (or list of qubits) + divisor (list): Quantum register (or list of qubits) + """ + # pylint: disable = pointless-statement, expression-not-assigned + + assert (len(remainder) == len(quotient) == len(divisor)) + + j = 0 + n = len(quotient) + + while j != n: + X | quotient[0] + with Control(eng, quotient[0]): + SubtractQuantum | (divisor, remainder) + CNOT | (remainder[-1], quotient[0]) + + AddQuantum | (divisor, remainder) + + remainder.insert(n, quotient[0]) + quotient.insert(n, remainder[0]) + del remainder[0] + del quotient[0] + j += 1 + + +def quantum_conditional_add_carry(eng, quint_a, quint_b, ctrl, z): + """ + Adds up two quantum integers if the control qubit is |1>, i.e., + + |a>|b>|ctrl>|z(0)z(1)> -> |a>|s(0)...s(n-1)>|ctrl>|s(n)z(1)> + (where s denotes the sum of a and b) + + If the control qubit is |0> no operation is performed: + + |a>|b>|ctrl>|z(0)z(1)> -> |a>|b>|ctrl>|z(0)z(1)> + + (only works if quint_a and quint_b are of the same size, ctrl is a + single qubit and z is a quantum register with 2 qubits. + + Args: + eng (MainEngine): ProjectQ MainEngine + quint_a (list): Quantum register (or list of qubits) + quint_b (list): Quantum register (or list of qubits) + ctrl (list): Control qubit + z (list): Quantum register with 2 qubits + + Notes: + Ancilla: 2, size: 7n - 4, toffoli: 3n + 2, depth: 5n. + + .. rubric:: References + + Quantum conditional add with no input carry from: + https://arxiv.org/pdf/1706.05113.pdf + """ + # pylint: disable = pointless-statement, expression-not-assigned + + assert len(quint_a) == len(quint_b) + assert len(ctrl) == 1 + assert len(z) == 2 + + n = len(quint_a) + + for i in range(1, n): + CNOT | (quint_a[i], quint_b[i]) + + with Control(eng, [quint_a[n - 1], ctrl[0]]): + X | z[0] + + for j in range(n - 2, 0, -1): + CNOT | (quint_a[j], quint_a[j + 1]) + + for k in range(0, n - 1): + with Control(eng, [quint_b[k], quint_a[k]]): + X | quint_a[k + 1] + + with Control(eng, [quint_b[n - 1], quint_a[n - 1]]): + X | z[1] + + with Control(eng, [ctrl[0], z[1]]): + X | z[0] + + with Control(eng, [quint_b[n - 1], quint_a[n - 1]]): + X | z[1] + + for l in range(n - 1, 0, -1): + with Control(eng, [ctrl[0], quint_a[l]]): + X | quint_b[l] + with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): + X | quint_a[l] + + with Control(eng, [quint_a[0], ctrl[0]]): + X | quint_b[0] + + for m in range(1, n - 1): + CNOT | (quint_a[m], quint_a[m + 1]) + + for n in range(1, n): + CNOT | (quint_a[n], quint_b[n]) + + +def quantum_multiplication(eng, quint_a, quint_b, product): + """ + Multiplies two quantum integers, i.e, + + |a>|b>|0> -> |a>|b>|a*b> + + (only works if quint_a and quint_b are of the same size, n qubits and + product has size 2n+1). + + Args: + eng (MainEngine): ProjectQ MainEngine + quint_a (list): Quantum register (or list of qubits) + quint_b (list): Quantum register (or list of qubits) + product (list): Quantum register (or list of qubits) storing + the result + + Notes: + Ancilla: 2n + 1, size: 7n^2 - 9n + 4, toffoli: 5n^2 - 4n, + depth: 3n^2 - 2. + + .. rubric:: References + + Quantum multiplication from: https://arxiv.org/abs/1706.05113. + + """ + # pylint: disable = pointless-statement, expression-not-assigned + + assert (len(quint_a) == len(quint_b)) + n = len(quint_a) + assert (len(product) == ((2 * n) + 1)) + + for i in range(0, n): + with Control(eng, [quint_a[i], quint_b[0]]): + X | product[i] + + with Control(eng, quint_b[1]): + AddQuantum | (quint_a[0:(n - 1)], product[1:n], + [product[n + 1], product[n + 2]]) + + for j in range(2, n): + with Control(eng, quint_b[j]): + AddQuantum | (quint_a[0:(n - 1)], product[(0 + j):(n - 1 + j)], + [product[n + j], product[n + j + 1]]) + + +def inverse_quantum_multiplication(eng, quint_a, quint_b, product): + """ + Inverse of the multiplication of two quantum integers, i.e, + + |a>|b>|a*b> -> |a>|b>|0> + + (only works if quint_a and quint_b are of the same size, n qubits and + product has size 2n+1) + + Args: + eng (MainEngine): ProjectQ MainEngine + quint_a (list): Quantum register (or list of qubits) + quint_b (list): Quantum register (or list of qubits) + product (list): Quantum register (or list of qubits) storing + the result + + """ + # pylint: disable = pointless-statement, expression-not-assigned + + assert len(quint_a) == len(quint_b) + n = len(quint_a) + assert len(product) == ((2 * n) + 1) + + for j in range(2, n): + with Control(eng, quint_b[j]): + SubtractQuantum | (quint_a[0:(n - 1)], product[(0 + j):( + n - 1 + j)], [product[n + j], product[n + j + 1]]) + for i in range(0, n): + with Control(eng, [quint_a[i], quint_b[0]]): + X | product[i] + + with Control(eng, quint_b[1]): + SubtractQuantum | (quint_a[0:(n - 1)], product[1:n], + [product[n + 1], product[n + 2]]) diff --git a/projectq/libs/math/_quantummath_test.py b/projectq/libs/math/_quantummath_test.py new file mode 100644 index 000000000..f3c8dfee5 --- /dev/null +++ b/projectq/libs/math/_quantummath_test.py @@ -0,0 +1,310 @@ +# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from projectq import MainEngine +from projectq.cengines import (InstructionFilter, AutoReplacer, + DecompositionRuleSet) +from projectq.backends import Simulator +from projectq.ops import (All, BasicMathGate, ClassicalInstructionGate, + Measure, X) + +from projectq.setups.decompositions import swap2cnot + +import projectq.libs.math +from projectq.libs.math import ( + AddQuantum, + SubtractQuantum, + ComparatorQuantum, + DivideQuantum, + MultiplyQuantum, +) + +from projectq.meta import Control, Compute, Uncompute + + +def print_all_probabilities(eng, qureg): + i = 0 + y = len(qureg) + while i < (2**y): + qubit_list = [int(x) for x in list(('{0:0b}'.format(i)).zfill(y))] + qubit_list = qubit_list[::-1] + l = eng.backend.get_probability(qubit_list, qureg) + if l != 0.0: + print(l, qubit_list, i) + i += 1 + + +def init(engine, quint, value): + for i in range(len(quint)): + if ((value >> i) & 1) == 1: + X | quint[i] + + +def no_math_emulation(eng, cmd): + if isinstance(cmd.gate, BasicMathGate): + return False + if isinstance(cmd.gate, ClassicalInstructionGate): + return True + try: + return len(cmd.gate.matrix) == 2 + except AttributeError: + return False + + +rule_set = DecompositionRuleSet(modules=[projectq.libs.math, swap2cnot]) + + +@pytest.fixture +def eng(): + return MainEngine(backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(no_math_emulation) + ]) + + +def test_quantum_adder(eng): + qureg_a = eng.allocate_qureg(4) + qureg_b = eng.allocate_qureg(4) + control_qubit = eng.allocate_qubit() + + init(eng, qureg_a, 2) + init(eng, qureg_b, 1) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 0, 0], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 0, 0], qureg_b)) + + with Control(eng, control_qubit): + AddQuantum | (qureg_a, qureg_b) + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 0, 0], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 0, 0], qureg_b)) + + X | control_qubit + + with Control(eng, control_qubit): + AddQuantum | (qureg_a, qureg_b) + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 0, 0], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 0, 0], qureg_b)) + + init(eng, qureg_a, 2) # reset + init(eng, qureg_b, 3) # reset + + c = eng.allocate_qubit() + init(eng, qureg_a, 15) + init(eng, qureg_b, 15) + + AddQuantum | (qureg_a, qureg_b, c) + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 1, 1], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 1, 1], qureg_b)) + assert 1. == pytest.approx(eng.backend.get_probability([1], c)) + + with Compute(eng): + with Control(eng, control_qubit): + AddQuantum | (qureg_a, qureg_b) + Uncompute(eng) + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 1, 1], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 1, 1], qureg_b)) + assert 1. == pytest.approx(eng.backend.get_probability([1], c)) + + AddQuantum | (qureg_a, qureg_b) + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 1, 1], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 1, 1], qureg_b)) + + with Compute(eng): + AddQuantum | (qureg_a, qureg_b) + Uncompute(eng) + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 1, 1], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 1, 1], qureg_b)) + + d = eng.allocate_qureg(2) + + with Compute(eng): + with Control(eng, control_qubit): + AddQuantum | (qureg_a, qureg_b, d) + Uncompute(eng) + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 1, 1], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 1, 1], qureg_b)) + assert 1. == pytest.approx(eng.backend.get_probability([0, 0], d)) + + All(Measure) | qureg_b + Measure | c + + +def test_quantumsubtraction(eng): + qureg_a = eng.allocate_qureg(4) + qureg_b = eng.allocate_qureg(4) + control_qubit = eng.allocate_qubit() + + init(eng, qureg_a, 5) + init(eng, qureg_b, 7) + + X | control_qubit + with Control(eng, control_qubit): + SubtractQuantum | (qureg_a, qureg_b) + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 1, 0, 0], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 0, 0, 0], qureg_b)) + + init(eng, qureg_a, 5) # reset + init(eng, qureg_b, 2) # reset + + init(eng, qureg_a, 5) + init(eng, qureg_b, 3) + + SubtractQuantum | (qureg_a, qureg_b) + + print_all_probabilities(eng, qureg_b) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 1, 0, 0], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 1, 1, 1, 0], qureg_b)) + + init(eng, qureg_a, 5) # reset + init(eng, qureg_b, 14) # reset + init(eng, qureg_a, 5) + init(eng, qureg_b, 3) + + with Compute(eng): + SubtractQuantum | (qureg_a, qureg_b) + Uncompute(eng) + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 1, 0, 0], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 0, 0, 0], qureg_b)) + All(Measure) | qureg_a + All(Measure) | qureg_b + + +def test_comparator(eng): + qureg_a = eng.allocate_qureg(3) + qureg_b = eng.allocate_qureg(3) + compare_qubit = eng.allocate_qubit() + + init(eng, qureg_a, 5) + init(eng, qureg_b, 3) + + ComparatorQuantum | (qureg_a, qureg_b, compare_qubit) + + assert 1. == pytest.approx(eng.backend.get_probability([1], compare_qubit)) + + All(Measure) | qureg_a + All(Measure) | qureg_b + Measure | compare_qubit + + +def test_quantumdivision(eng): + qureg_a = eng.allocate_qureg(4) + qureg_b = eng.allocate_qureg(4) + qureg_c = eng.allocate_qureg(4) + + init(eng, qureg_a, 10) + init(eng, qureg_c, 3) + + DivideQuantum | (qureg_a, qureg_b, qureg_c) + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 0, 0], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 0, 0], qureg_b)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 0, 0], qureg_c)) + + All(Measure) | qureg_a + All(Measure) | qureg_b + All(Measure) | qureg_c + + init(eng, qureg_a, 1) # reset + init(eng, qureg_b, 3) # reset + + init(eng, qureg_a, 11) + + with Compute(eng): + DivideQuantum | (qureg_a, qureg_b, qureg_c) + Uncompute(eng) + + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 0, 1], qureg_a)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 0], qureg_b)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 1, 0, 0], qureg_c)) + + All(Measure) | qureg_a + All(Measure) | qureg_b + All(Measure) | qureg_c + + +def test_quantummultiplication(eng): + qureg_a = eng.allocate_qureg(3) + qureg_b = eng.allocate_qureg(3) + qureg_c = eng.allocate_qureg(7) + + init(eng, qureg_a, 7) + init(eng, qureg_b, 3) + + MultiplyQuantum | (qureg_a, qureg_b, qureg_c) + + assert 1. == pytest.approx(eng.backend.get_probability([1, 1, 1], qureg_a)) + assert 1. == pytest.approx(eng.backend.get_probability([1, 1, 0], qureg_b)) + assert 1. == pytest.approx( + eng.backend.get_probability([1, 0, 1, 0, 1, 0, 0], qureg_c)) + + All(Measure) | qureg_a + All(Measure) | qureg_b + All(Measure) | qureg_c + + init(eng, qureg_a, 7) + init(eng, qureg_b, 3) + init(eng, qureg_c, 21) + + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 0, 0, 0, 0], qureg_c)) + init(eng, qureg_a, 2) + init(eng, qureg_b, 3) + + with Compute(eng): + MultiplyQuantum | (qureg_a, qureg_b, qureg_c) + Uncompute(eng) + + assert 1. == pytest.approx(eng.backend.get_probability([0, 1, 0], qureg_a)) + assert 1. == pytest.approx(eng.backend.get_probability([1, 1, 0], qureg_b)) + assert 1. == pytest.approx( + eng.backend.get_probability([0, 0, 0, 0, 0, 0, 0], qureg_c)) diff --git a/pytest.ini b/pytest.ini index d17d4ce2e..9dd4e3a91 100755 --- a/pytest.ini +++ b/pytest.ini @@ -5,3 +5,5 @@ filterwarnings = error ignore:the matrix subclass is not the recommended way:PendingDeprecationWarning ignore:Matplotlib is currently using agg, which is a non-GUI backend, so cannot show the figure. + ignore:the imp module is deprecated in favour of importlib + ignore::pytest.PytestUnraisableExceptionWarning \ No newline at end of file From cee46887e73fc50822a48c1711e0719ff1ca5475 Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Mon, 1 Mar 2021 10:17:47 +0100 Subject: [PATCH 04/11] Update .travis.yml configuration & add .coveragerc (#389) * Update .travis.yml configuration * Only do coveralls for one job * Fix failing tests * Tweak Mac OS build * More tweaks to Mac OS builds * Fix error with submitting to coveralls.io * Add .coveragerc * Add missing revkit dependency to .travis.yml * Temporarily disable Mac OS and Windows builds - Disable the builds until the project will be migrated to travis-ci.com due to the new credits plans on Travis-CI. --- .coveragerc | 3 + .travis.yml | 107 +++++++++++++++++++------------ projectq/backends/_sim/_pysim.py | 7 +- projectq/ops/_qubit_operator.py | 2 +- 4 files changed, 75 insertions(+), 44 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..1f6874dc9 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] + +omit = *_test.py diff --git a/.travis.yml b/.travis.yml index ea518e9ef..831f3baf3 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,52 +1,75 @@ -sudo: false +# ============================================================================== + +addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + # update: true + packages: + - gcc-9 + - g++-9 + - build-essential + - python3 + - python3-pip + + homebrew: + update: false + +# ============================================================================== + +env: + global: + - OMP_NUM_THREADS=1 + - CC=gcc-9 + - CXX=g++-9 + +os: linux language: python -matrix: - include: - - os: linux - python: "2.7" - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['gcc-4.9', 'g++-4.9', 'gcc-7', 'g++-7'] - env: CC=gcc-4.9 CXX=g++-4.9 PYTHON=2.7 - - os: linux - python: "3.4" - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['gcc-4.9', 'g++-4.9', 'gcc-7', 'g++-7'] - env: CC=gcc-4.9 CXX=g++-4.9 PYTHON=3.4 - - os: linux - python: "3.5" - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['gcc-4.9', 'g++-4.9', 'gcc-7', 'g++-7'] - env: CC=gcc-4.9 CXX=g++-4.9 PYTHON=3.5 - - os: linux - python: "3.6" - addons: - apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['gcc-4.9', 'g++-4.9', 'gcc-7', 'g++-7'] - env: CC=gcc-4.9 CXX=g++-4.9 PYTHON=3.6 + +python: + - 3.5 + - 3.6 + - 3.7 + - 3.8 + - 3.9 + +jobs: + fast_finish: true + # Limit the number of builds to use less credits on Travis-CI + # include: + # - os: osx + # osx_image: xcode12.2 + # language: shell + # name: "Mac OS Python Homebrew" + # env: CC=clang CXX=clang++ + # before_install: + # - clang++ --version + # - os: windows + # name: "Windows Python 3.8" + # language: shell + # before_install: + # - unset CC CXX + # - choco install python3 --version 3.8.8 + # - ln -s /c/Python38/python.exe /c/Python38/python3.exe + # - python3 -m pip install --upgrade pip + # env: PATH=/c/Python38:/c/Python38/Scripts:$PATH + +# ============================================================================== +# Installation and testing install: - - if [ "${PYTHON:0:1}" = "3" ]; then export PY=3; fi - - pip$PY install --upgrade pip setuptools wheel - - pip$PY install --only-binary=numpy,scipy numpy scipy - - pip$PY install -r requirements.txt - - pip$PY install pytest-cov - - pip$PY install coveralls - - CC=g++-7 pip$PY install revkit - - if [ "${PYTHON:0:1}" = "3" ]; then pip$PY install dormouse; fi - - pip$PY install -e . + - env + - python3 -m pip install -U pip setuptools wheel + - python3 -m pip install -U pybind11 dormouse revkit flaky pytest-cov coveralls + - python3 -m pip install -r requirements.txt + - python3 -m pip install -ve . before_script: - "echo 'backend: Agg' > matplotlibrc" -# command to run tests -script: export OMP_NUM_THREADS=1 && pytest projectq --cov projectq -p no:warnings +script: + - python3 -m pytest projectq --cov projectq -p no:warnings after_success: - coveralls + +# ============================================================================== diff --git a/projectq/backends/_sim/_pysim.py b/projectq/backends/_sim/_pysim.py index 4faf811f6..58bff2ec2 100755 --- a/projectq/backends/_sim/_pysim.py +++ b/projectq/backends/_sim/_pysim.py @@ -20,6 +20,11 @@ import random import numpy as _np +import os + +_USE_REFCHECK = True +if 'TRAVIS' in os.environ: + _USE_REFCHECK = False class Simulator(object): @@ -110,7 +115,7 @@ def allocate_qubit(self, ID): """ self._map[ID] = self._num_qubits self._num_qubits += 1 - self._state.resize(1 << self._num_qubits) + self._state.resize(1 << self._num_qubits, refcheck=_USE_REFCHECK) def get_classical_value(self, ID, tol=1.e-10): """ diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index 7fb65d1b2..ece98d698 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -159,7 +159,7 @@ def __init__(self, term=None, coefficient=1.): if term is None: return elif isinstance(term, tuple): - if term is (): + if term == (): self.terms[()] = coefficient else: # Test that input is a tuple of tuples and correct action From fb548ef4b3544985ba0b73095d5c8794ed38f43c Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 19 Apr 2021 16:23:26 +0200 Subject: [PATCH 05/11] AWS Braket service backend (#388) * Add AWS Braket service devices as backends * Add AWS Braket service devices as backends. Correct a braket in README * Add AWS Braket service devices as backends. Pass pycodestyle * AWS Braket service backend. Corrections as per the review * AWS Braket service backend. Correct test to use approx instead of == * AWS Braket service backend. Add tests to raise the coverage * AWS Braket service backend. Second review adjustments * Fix import errors when boto3 is not installed + use warnings.warn() * Fix condition when setting the number of controls * Minor adjustments in _awsbraket.py - Better handling of daggered gates - Some minor code cleanup * Make sure that unit tests are skipped if boto3 is not installed * Rapid fix to make sure that tests can be repeated without errors * Fixes for _awsbraket.py and its associated tests - Minor code cleanup - Modify functional tests to work with and without mapper - Fix issue when repeating some tests more than once - Add missing test for case where AWS Braket backend is used as a compiler engine * Minor adjustments to _awsbraket_boto3_client.py and related tests - Tweak user messages slightly - Add test to cover missing execution paths * Remove mapper in Jupyter notebook * Cleanup tests for _awsbraket.py + remove unneeded catch of TypeError * Adjust .coveragerc * Cleanup tests for _awsbraket_boto3_client.py * Fix erroneous license header * Mention installing ProjectQ with the 'braket' extra in README * Remove unneeded call to is_available in AWSBraketBackend + ... ... improve basic functional test. * Re-indent _awsbraket_boto3_client.py * Some more cleanup for _awsbraket.py * Some more cleanup for _awsbraket_boto3_client.py * Remove trivial mapper in default setup for AWS Braket backend * Fix test failure Co-authored-by: Damien Nguyen --- .coveragerc | 1 + .travis.yml | 2 +- README.rst | 40 +- docs/tutorials.rst | 8 + examples/awsbraket.ipynb | 210 +++++++ projectq/backends/__init__.py | 2 + projectq/backends/_awsbraket/__init__.py | 26 + projectq/backends/_awsbraket/_awsbraket.py | 414 +++++++++++++ .../_awsbraket/_awsbraket_boto3_client.py | 490 +++++++++++++++ .../_awsbraket_boto3_client_test.py | 290 +++++++++ .../_awsbraket_boto3_client_test_fixtures.py | 407 +++++++++++++ .../backends/_awsbraket/_awsbraket_test.py | 563 ++++++++++++++++++ .../_awsbraket/_awsbraket_test_fixtures.py | 240 ++++++++ projectq/setups/awsbraket.py | 75 +++ projectq/setups/awsbraket_test.py | 133 +++++ setup.py | 1 + 16 files changed, 2900 insertions(+), 2 deletions(-) create mode 100644 examples/awsbraket.ipynb create mode 100644 projectq/backends/_awsbraket/__init__.py create mode 100755 projectq/backends/_awsbraket/_awsbraket.py create mode 100755 projectq/backends/_awsbraket/_awsbraket_boto3_client.py create mode 100644 projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py create mode 100644 projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py create mode 100644 projectq/backends/_awsbraket/_awsbraket_test.py create mode 100644 projectq/backends/_awsbraket/_awsbraket_test_fixtures.py create mode 100644 projectq/setups/awsbraket.py create mode 100644 projectq/setups/awsbraket_test.py diff --git a/.coveragerc b/.coveragerc index 1f6874dc9..1650dd93d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,4 @@ [run] omit = *_test.py + *_fixtures.py diff --git a/.travis.yml b/.travis.yml index 831f3baf3..0dd408af0 100755 --- a/.travis.yml +++ b/.travis.yml @@ -59,7 +59,7 @@ jobs: install: - env - python3 -m pip install -U pip setuptools wheel - - python3 -m pip install -U pybind11 dormouse revkit flaky pytest-cov coveralls + - python3 -m pip install -U pybind11 dormouse revkit flaky pytest-cov coveralls boto3 - python3 -m pip install -r requirements.txt - python3 -m pip install -ve . diff --git a/README.rst b/README.rst index 7b6e93beb..4a14403bc 100755 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ targeting various types of hardware, a high-performance quantum computer simulator with emulation capabilities, and various compiler plug-ins. This allows users to -- run quantum programs on the IBM Quantum Experience chip +- run quantum programs on the IBM Quantum Experience chip, AQT devices or AWS Braket service provided devices - simulate quantum programs on classical computers - emulate quantum programs at a higher level of abstraction (e.g., mimicking the action of large oracles instead of compiling them to @@ -134,6 +134,44 @@ To run a program on the AQT trapped ion quantum computer, choose the `AQTBackend engine_list=compiler_engines) +**Running a quantum program on a AWS Braket provided device** + +To run a program on some of the devices provided by the AWS Braket service, +choose the `AWSBraketBackend`. The currend devices supported are Aspen-8 from Rigetti, +IonQ from IonQ and the state vector simulator SV1: + +.. code-block:: python + + from projectq.backends import AWSBraketBackend + + creds = { + 'AWS_ACCESS_KEY_ID': 'your_aws_access_key_id', + 'AWS_SECRET_KEY': 'your_aws_secret_key', + } + + s3_folder = ['S3Bucket', 'S3Directory'] + device='IonQ' + eng = MainEngine(AWSBraketBackend(use_hardware=True, credentials=creds, s3_folder=s3_folder, + num_runs=1024, verbose=False, device=device), + engine_list=[]) + + +.. note:: + + In order to use the AWSBraketBackend, you need to install ProjectQ with the 'braket' extra requirement: + + .. code-block:: bash + + python3 -m pip install projectq[braket] + + or + + .. code-block:: bash + + cd /path/to/projectq/source/code + python3 -m pip install -ve .[braket] + + **Classically simulate a quantum program** ProjectQ has a high-performance simulator which allows simulating up to about 30 qubits on a regular laptop. See the `simulator tutorial `__ for more information. Using the emulation features of our simulator (fast classical shortcuts), one can easily emulate Shor's algorithm for problem sizes for which a quantum computer would require above 50 qubits, see our `example codes `__. diff --git a/docs/tutorials.rst b/docs/tutorials.rst index cec2e75e7..cc7800ed9 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -44,6 +44,14 @@ ProjectQ comes with a high-performance quantum simulator written in C++. Please .. note:: ProjectQ should be installed on each computer individually as the C++ simulator compilation creates binaries which are optimized for the specific hardware on which it is being installed (potentially using our AVX version and `-march=native`). Therefore, sharing the same ProjectQ installation across different hardware may cause some problems. +**Install AWS Braket Backend requirement** + +AWS Braket Backend requires the use of the official AWS SDK for Python, Boto3. This is an extra requirement only needed if you plan to use the AWS Braket Backend. To install ProjectQ inluding this requirement you can include it in the installation instruction as + +.. code-block:: bash + + python -m pip install --user projectq[braket] + Detailed instructions and OS-specific hints ------------------------------------------- diff --git a/examples/awsbraket.ipynb b/examples/awsbraket.ipynb new file mode 100644 index 000000000..0e3a1935a --- /dev/null +++ b/examples/awsbraket.ipynb @@ -0,0 +1,210 @@ +{ + "metadata": { + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.10-final" + }, + "orig_nbformat": 2, + "kernelspec": { + "name": "python3", + "display_name": "Python 3.7.10 64-bit", + "metadata": { + "interpreter": { + "hash": "fd69f43f58546b570e94fd7eba7b65e6bcc7a5bbc4eab0408017d18902915d69" + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 2, + "cells": [ + { + "source": [ + "# Running ProjectQ code on AWS Braket service provided devices\n", + "## Compiling code for AWS Braket Service\n", + "\n", + "In this tutorial we will see how to run code on some of the devices provided by the Amazon AWS Braket service. The AWS Braket devices supported are: the State Vector Simulator 'SV1', the Rigetti device 'Aspen-8' and the IonQ device 'IonQ'\n", + "\n", + "You need to have a valid AWS account, created a pair of access key/secret key, and have activated the braket service. As part of the activation of the service, a specific S3 bucket and folder associated to the service should be configured.\n", + "\n", + "First we need to do the required imports. That includes the mail compiler engine (MainEngine), the backend (AWSBraketBackend in this case) and the operations to be used in the cicuit" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from projectq import MainEngine\n", + "from projectq.backends import AWSBraketBackend\n", + "from projectq.ops import Measure, H, C, X, All\n" + ] + }, + { + "source": [ + "Prior to the instantiation of the backend we need to configure the credentials, the S3 storage folder and the device to be used (in the example the State Vector Simulator SV1)" + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "creds = {\n", + " 'AWS_ACCESS_KEY_ID': 'aws_access_key_id',\n", + " 'AWS_SECRET_KEY': 'aws_secret_key',\n", + " } # replace with your Access key and Secret key\n", + "\n", + "s3_folder = ['S3Bucket', 'S3Directory'] # replace with your S3 bucket and directory\n", + "\n", + "device = 'SV1' # replace by the device you want to use" + ] + }, + { + "source": [ + "Next we instantiate the engine with the AWSBraketBackend including the credentials and S3 configuration. By setting the 'use_hardware' parameter to False we indicate the use of the Simulator. In addition we set the number of times we want to run the circuit and the interval in secons to ask for the results. For a complete list of parameters and descriptions, please check the documentation." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "eng = MainEngine(AWSBraketBackend(use_hardware=False,\n", + " credentials=creds,\n", + " s3_folder=s3_folder,\n", + " num_runs=10,\n", + " interval=10))" + ] + }, + { + "source": [ + "We can now allocate the required qubits and create the circuit to be run. With the last instruction we ask the backend to run the circuit." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Allocate the required qubits\n", + "qureg = eng.allocate_qureg(3)\n", + "\n", + "# Create the circuit. In this example a quantum teleportation algorithms that teleports the first qubit to the third one.\n", + "H | qureg[0]\n", + "H | qureg[1]\n", + "C(X) | (qureg[1], qureg[2])\n", + "C(X) | (qureg[0], qureg[1])\n", + "H | qureg[0]\n", + "C(X) | (qureg[1], qureg[2])\n", + "\n", + "# At the end we measure the qubits to get the results; should be all-0 or all-1\n", + "All(Measure) | qureg\n", + "\n", + "# And run the circuit\n", + "eng.flush()\n" + ] + }, + { + "source": [ + "The backend will automatically create the task and generate a unique identifier (the task Arn) that can be used to recover the status of the task and results later on.\n", + "\n", + "Once the circuit is executed the indicated number of times, the results are stored in the S3 folder configured previously and can be recovered to obtain the probabilities of each of the states." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Obtain and print the probabilies of the states\n", + "prob_dict = eng.backend.get_probabilities(qureg)\n", + "print(\"Probabilites for each of the results: \", prob_dict)" + ] + }, + { + "source": [ + "## Retrieve results form a previous execution\n", + "\n", + "We can retrieve the result later on (of this job or a previously executed one) using the task Arn provided when it was run. In addition, you have to remember the amount of qubits involved in the job and the order you used. The latter is required since we need to set up a mapping for the qubits when retrieving results of a previously executed job.\n", + "\n", + "To retrieve the results we need to configure the backend including the parameter 'retrieve_execution' set to the Task Arn of the job. To be able to get the probabilities of each state we need to configure the qubits and ask the backend to get the results." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set the Task Arn of the job to be retrieved and instantiate the engine with the AWSBraketBackend\n", + "task_arn = 'your_task_arn' # replace with the actual TaskArn you want to use\n", + "\n", + "eng1 = MainEngine(AWSBraketBackend(retrieve_execution=task_arn, credentials=creds, num_retries=2, verbose=True))\n", + "\n", + "# Configure the qubits to get the states probabilies\n", + "qureg1 = eng1.allocate_qureg(3)\n", + "\n", + "# Ask the backend to retrieve the results\n", + "eng1.flush()\n", + "\n", + "# Obtain and print the probabilities of the states\n", + "prob_dict1 = eng1.backend.get_probabilities(qureg1)\n", + "print(\"Probabilities \", prob_dict1)\n" + ] + }, + { + "source": [ + "We can plot an histogram with the probabilities as well." + ], + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "from projectq.libs.hist import histogram\n", + "\n", + "histogram(eng1.backend, qureg1)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ] +} diff --git a/projectq/backends/__init__.py b/projectq/backends/__init__.py index f35a3acec..0d65fbab8 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -25,6 +25,7 @@ circuit) * an interface to the IBM Quantum Experience chip (and simulator). * an interface to the AQT trapped ion system (and simulator). +* an interface to the AWS Braket service decives (and simulators) """ from ._printer import CommandPrinter from ._circuits import CircuitDrawer, CircuitDrawerMatplotlib @@ -32,3 +33,4 @@ from ._resource import ResourceCounter from ._ibm import IBMBackend from ._aqt import AQTBackend +from ._awsbraket import AWSBraketBackend diff --git a/projectq/backends/_awsbraket/__init__.py b/projectq/backends/_awsbraket/__init__.py new file mode 100644 index 000000000..8641d77b9 --- /dev/null +++ b/projectq/backends/_awsbraket/__init__.py @@ -0,0 +1,26 @@ +# Copyright 2021 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. + +try: + from ._awsbraket import AWSBraketBackend +except ImportError: # pragma: no cover + import warnings + warnings.warn("Failed to import one of the dependencies required to use " + "the Amazon Braket Backend.\n" + "Did you install ProjectQ using the [braket] extra? " + "(python3 -m pip install projectq[braket])") + + # Make sure that the symbol is defined + class AWSBraketBackend: + pass diff --git a/projectq/backends/_awsbraket/_awsbraket.py b/projectq/backends/_awsbraket/_awsbraket.py new file mode 100755 index 000000000..fbb38f034 --- /dev/null +++ b/projectq/backends/_awsbraket/_awsbraket.py @@ -0,0 +1,414 @@ +# Copyright 2021 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. +""" Back-end to run quantum program on AWS Braket provided devices.""" + +import random +import json + +from projectq.cengines import BasicEngine +from projectq.meta import get_control_count, LogicalQubitIDTag +from projectq.types import WeakQubitRef +from projectq.ops import (R, SwapGate, HGate, Rx, Ry, Rz, SGate, Sdag, TGate, + Tdag, XGate, YGate, ZGate, SqrtXGate, Measure, + Allocate, Deallocate, Barrier, FlushGate, + DaggeredGate) +# TODO: Add MatrixGate to cover the unitary operation in the SV1 simulator + +from ._awsbraket_boto3_client import send, retrieve + + +class AWSBraketBackend(BasicEngine): + """ + The AWS Braket Backend class, which stores the circuit, + transforms it to Braket compatible, + and sends the circuit through the Boto3 and Amazon Braket SDK. + """ + def __init__(self, + use_hardware=False, + num_runs=1000, + verbose=False, + credentials=None, + s3_folder=None, + device='Aspen-8', + num_retries=30, + interval=1, + retrieve_execution=None): + """ + Initialize the Backend object. + + Args: + use_hardware (bool): If True, the code is run on one of the AWS + Braket backends, by default on the Rigetti Aspen-8 chip + (instead of using the AWS Braket SV1 Simulator) + num_runs (int): Number of runs to collect statistics. + (default is 1000) + verbose (bool): If True, statistics are printed, in addition to the + measurement result being registered (at the end of the + circuit). + credentials (dict): mapping the AWS key credentials as the + AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + device (str): name of the device to use. Rigetti Aspen-8 by + default. Valid names are "Aspen-8", "IonQ Device" and "SV1" + num_retries (int): Number of times to retry to obtain results from + AWS Braket. (default is 30) + interval (float, int): Number of seconds between successive + attempts to obtain results from AWS Braket. (default is 1) + retrieve_execution (str): TaskArn to retrieve instead of re-running + the circuit (e.g., if previous run timed out). The TaskArns + have the form: + "arn:aws:braket:us-east-1:123456789012:quantum-task/5766032b-2b47-4bf9-cg00-f11851g4015b" + """ + BasicEngine.__init__(self) + self._reset() + if use_hardware: + self.device = device + else: + self.device = 'SV1' + self._clear = False + self._num_runs = num_runs + self._verbose = verbose + self._credentials = credentials + self._s3_folder = s3_folder + self._num_retries = num_retries + self._interval = interval + self._probabilities = dict() + self._circuit = "" + self._measured_ids = [] + self._allocated_qubits = set() + self._retrieve_execution = retrieve_execution + + # Dictionary to translate the gates from ProjectQ to AWSBraket + self._gationary = { + XGate: 'x', + YGate: 'y', + ZGate: 'z', + HGate: 'h', + R: 'phaseshift', + Rx: 'rx', + Ry: 'ry', + Rz: 'rz', + SGate: 's', # NB: Sdag is 'si' + TGate: 't', # NB: Tdag is 'ti' + SwapGate: 'swap', + SqrtXGate: 'v' + } + + # Static head and tail to be added to the circuit + # to build the "action". + self._circuithead = '{"braketSchemaHeader": \ +{"name": "braket.ir.jaqcd.program", "version": "1"}, \ +"results": [], "basis_rotation_instructions": [], \ +"instructions": [' + + self._circuittail = ']}' + + def is_available(self, cmd): + """ + Return true if the command can be executed. + + Depending on the device chosen, the operations available differ. + + The operations avialable for the Aspen-8 Rigetti device are: + - "cz" = Control Z, "xy" = Not available in ProjectQ, + "ccnot" = Toffoli (ie. controlled CNOT), "cnot" = Control X, + "cphaseshift" = Control R, + "cphaseshift00" "cphaseshift01" "cphaseshift10" = Not available + in ProjectQ, + "cswap" = Control Swap, "h" = H, "i" = Identity, not in ProjectQ, + "iswap" = Not available in ProjectQ, "phaseshift" = R, + "pswap" = Not available in ProjectQ, "rx" = Rx, "ry" = Ry, "rz" = Rz, + "s" = S, "si" = Sdag, "swap" = Swap, "t" = T, "ti" = Tdag, + "x" = X, "y" = Y, "z" = Z + + The operations available for the IonQ Device are: + - "x" = X, "y" = Y, "z" = Z, "rx" = Rx, "ry" = Ry, "rz" = Rz, "h", H, + "cnot" = Control X, "s" = S, "si" = Sdag, "t" = T, "ti" = Tdag, + "v" = SqrtX, "vi" = Not available in ProjectQ, + "xx" "yy" "zz" = Not available in ProjectQ, "swap" = Swap, + "i" = Identity, not in ProjectQ + + The operations available for the StateVector simulator (SV1) are + the union of the ones for Rigetti Aspen-8 and IonQ Device plus some + more: + - "cy" = Control Y, "unitary" = Arbitrary unitary gate defined as a + matrix equivalent to the MatrixGate in ProjectQ, "xy" = Not available + in ProjectQ + + Args: + cmd (Command): Command for which to check availability + """ + + gate = cmd.gate + if gate in (Measure, Allocate, Deallocate, Barrier): + return True + + if self.device == 'Aspen-8': + if get_control_count(cmd) == 2: + return isinstance(gate, XGate) + if get_control_count(cmd) == 1: + return isinstance(gate, (R, ZGate, XGate, SwapGate)) + if get_control_count(cmd) == 0: + return isinstance( + gate, (R, Rx, Ry, Rz, XGate, YGate, ZGate, HGate, SGate, + TGate, SwapGate)) or gate in (Sdag, Tdag) + + if self.device == 'IonQ Device': + if get_control_count(cmd) == 1: + return isinstance(gate, XGate) + if get_control_count(cmd) == 0: + return isinstance( + gate, (Rx, Ry, Rz, XGate, YGate, ZGate, HGate, SGate, + TGate, SqrtXGate, SwapGate)) or gate in (Sdag, Tdag) + + if self.device == 'SV1': + if get_control_count(cmd) == 2: + return isinstance(gate, XGate) + if get_control_count(cmd) == 1: + return isinstance(gate, (R, ZGate, YGate, XGate, SwapGate)) + if get_control_count(cmd) == 0: + # TODO: add MatrixGate to cover the unitary operation + # TODO: Missing XY gate in ProjectQ + return isinstance( + gate, (R, Rx, Ry, Rz, XGate, YGate, ZGate, HGate, SGate, + TGate, SqrtXGate, SwapGate)) or gate in (Sdag, Tdag) + return False + + def _reset(self): + """ Reset all temporary variables (after flush gate). """ + self._clear = True + self._measured_ids = [] + + def _store(self, cmd): + """ + Temporarily store the command cmd. + + Translates the command and stores it in a local variable + (self._circuit) in JSON format. + + Args: + cmd: Command to store + """ + if self._clear: + self._probabilities = dict() + self._clear = False + self._circuit = "" + self._allocated_qubits = set() + + gate = cmd.gate + num_controls = get_control_count(cmd) + gate_type = (type(gate) if not isinstance(gate, DaggeredGate) else + type(gate._gate)) + + if gate == Allocate: + self._allocated_qubits.add(cmd.qubits[0][0].id) + return + if gate in (Deallocate, Barrier): + return + if gate == Measure: + assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 + qb_id = cmd.qubits[0][0].id + logical_id = None + for tag in cmd.tags: + if isinstance(tag, LogicalQubitIDTag): + logical_id = tag.logical_qubit_id + break + self._measured_ids.append( + logical_id if logical_id is not None else qb_id) + return + + # All other supported gate types + json_cmd = {} + + if num_controls > 1: + json_cmd['controls'] = [qb.id for qb in cmd.control_qubits] + elif num_controls == 1: + json_cmd['control'] = cmd.control_qubits[0].id + + qubits = [qb.id for qureg in cmd.qubits for qb in qureg] + if len(qubits) > 1: + json_cmd['targets'] = qubits + else: + json_cmd['target'] = qubits[0] + + if isinstance(gate, (R, Rx, Ry, Rz)): + json_cmd['angle'] = gate.angle + + if isinstance(gate, DaggeredGate): + json_cmd['type'] = ('c' * num_controls + self._gationary[gate_type] + + 'i') + elif isinstance(gate, (XGate)) and num_controls > 0: + json_cmd['type'] = 'c' * (num_controls - 1) + 'cnot' + else: + json_cmd['type'] = 'c' * num_controls + self._gationary[gate_type] + + self._circuit += json.dumps(json_cmd) + ", " + + # TODO: Add unitary for the SV1 simulator as MatrixGate + + def _logical_to_physical(self, qb_id): + """ + Return the physical location of the qubit with the given logical id. + + Args: + qb_id (int): ID of the logical qubit whose position should be + returned. + """ + if self.main_engine.mapper is not None: + mapping = self.main_engine.mapper.current_mapping + if qb_id not in mapping: + raise RuntimeError( + "Unknown qubit id {} in current mapping. Please make sure " + "eng.flush() was called and that the qubit " + "was eliminated during optimization.".format(qb_id)) + return mapping[qb_id] + return 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 + the first qubit in the supplied quantum register. + + Args: + qureg (list): Quantum register determining the order of the + qubits. + + Returns: + probability_dict (dict): Dictionary mapping n-bit strings to + probabilities. + + Raises: + RuntimeError: If no data is available (i.e., if the circuit has not + been executed). Or if a qubit was supplied which was not + present in the circuit (might have gotten optimized away). + + Warning: + Only call this function after the circuit has been executed! + + This is maintained in the same form of IBM and AQT for + compatibility but in AWSBraket, a previously executed circuit will + store the results in the S3 bucket and it can be retreived at any + point in time thereafter. + No circuit execution should be required at the time of retrieving + the results and probabilities if the circuit has already been + executed. + In order to obtain the probabilities of a previous job you have to + get the TaskArn and remember the qubits and ordering used in the + original job. + + """ + if len(self._probabilities) == 0: + raise RuntimeError("Please, run the circuit first!") + + probability_dict = dict() + for state in self._probabilities: + mapped_state = ['0'] * len(qureg) + for i, _ in enumerate(qureg): + assert self._logical_to_physical(qureg[i].id) < len(state) + mapped_state[i] = state[self._logical_to_physical(qureg[i].id)] + probability = self._probabilities[state] + 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 + + def _run(self): + """ + Run the circuit. + + Send the circuit via the AWS Boto3 SDK. Use the provided Access Key and + Secret key or ask for them if not provided + """ + # NB: the AWS Braket API does not require explicit measurement commands + # at the end of a circuit; after running any circuit, all qubits are + # implicitly measured. + # Also, AWS Braket currently does not support intermediate + # measurements. + + # In Braket the results for the jobs are stored in S3. + # You can recover the results from previous jobs using the TaskArn + # (self._retrieve_execution). + if self._retrieve_execution is not None: + res = retrieve(credentials=self._credentials, + taskArn=self._retrieve_execution, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose) + else: + # Return if no operations have been added. + if not self._circuit: + return + + n_qubit = len(self._allocated_qubits) + info = {} + info['circuit'] = self._circuithead + \ + self._circuit.rstrip(', ') + \ + self._circuittail + info['nq'] = n_qubit + info['shots'] = self._num_runs + info['backend'] = {'name': self.device} + res = send(info, + device=self.device, + credentials=self._credentials, + s3_folder=self._s3_folder, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose) + + counts = res + + # Determine random outcome + P = random.random() + p_sum = 0. + measured = "" + for state in counts: + probability = counts[state] + p_sum += probability + star = "" + if p_sum >= P and measured == "": + measured = state + star = "*" + self._probabilities[state] = probability + if self._verbose and probability > 0: + print(state + " with p = " + str(probability) + star) + + # register measurement result + for qubit_id in self._measured_ids: + result = int(measured[self._logical_to_physical(qubit_id)]) + self.main_engine.set_measurement_result( + WeakQubitRef(self.main_engine, qubit_id), result) + self._reset() + + def receive(self, command_list): + """ + Receives a command list and, for each command, stores it until + completion. + + Args: + command_list: List of commands to execute + """ + for cmd in command_list: + if not isinstance(cmd.gate, FlushGate): + self._store(cmd) + else: + self._run() + self._reset() + if not self.is_last_engine: + self.send([cmd]) diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py new file mode 100755 index 000000000..0e74b3013 --- /dev/null +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py @@ -0,0 +1,490 @@ +# Copyright 2021 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. +""" +Back-end to run quantum program on AWS Braket supported devices. + +This backend requires the official AWS SDK for Python, Boto3. +The installation is very simple +> pip install boto3 +""" + +import getpass +import signal +import re +import time +import boto3 +import botocore + +import json + + +class AWSBraket(): + """ + Manage a session between ProjectQ and AWS Braket service. + """ + def __init__(self): + self.backends = dict() + self.timeout = 5.0 + self._credentials = dict() + self._s3_folder = [] + + def _authenticate(self, credentials=None): + """ + Args: + credentials (dict): mapping the AWS key credentials as the + AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + """ + if credentials is None: # pragma: no cover + credentials['AWS_ACCESS_KEY_ID'] = getpass.getpass( + prompt="Enter AWS_ACCESS_KEY_ID: ") + credentials['AWS_SECRET_KEY'] = getpass.getpass( + prompt="Enter AWS_SECRET_KEY: ") + + self._credentials = credentials + + def _get_s3_folder(self, s3_folder=None): + """ + Args: + s3_folder (list): contains the S3 bucket and directory to store the + results. + """ + if s3_folder is None: # pragma: no cover + S3Bucket = input("Enter the S3 Bucket configured in Braket: ") + S3Directory = input( + "Enter the Directory created in the S3 Bucket: ") + s3_folder = [S3Bucket, S3Directory] + + self._s3_folder = s3_folder + + def get_list_devices(self, verbose=False): + """ + Get the list of available devices with their basic properties + + Args: + verbose (bool): print the returned dictionnary if True + + Returns: + (dict) backends dictionary by deviceName, containing the qubit size + 'nq', the coupling map 'coupling_map' if applicable (IonQ + Device as an ion device is having full connectivity) and the + Schema Header version 'version', because it seems that no + device version is available by now + """ + # TODO: refresh region_names if more regions get devices available + self.backends = dict() + region_names = ['us-west-1', 'us-east-1'] + for region in region_names: + client = boto3.client( + 'braket', + region_name=region, + aws_access_key_id=self._credentials['AWS_ACCESS_KEY_ID'], + aws_secret_access_key=self._credentials['AWS_SECRET_KEY']) + filters = [] + devicelist = client.search_devices(filters=filters) + for result in devicelist['devices']: + if result['deviceType'] not in ['QPU', 'SIMULATOR']: + continue + if result['deviceType'] == 'QPU': + deviceCapabilities = json.loads( + client.get_device(deviceArn=result['deviceArn']) + ['deviceCapabilities']) + self.backends[result['deviceName']] = { + 'nq': + deviceCapabilities['paradigm']['qubitCount'], + 'coupling_map': + deviceCapabilities['paradigm']['connectivity'] + ['connectivityGraph'], + 'version': + deviceCapabilities['braketSchemaHeader']['version'], + 'location': + region, # deviceCapabilities['service']['deviceLocation'], + 'deviceArn': + result['deviceArn'], + 'deviceParameters': + deviceCapabilities['deviceParameters']['properties'] + ['braketSchemaHeader']['const'], + 'deviceModelParameters': + deviceCapabilities['deviceParameters']['definitions'] + ['GateModelParameters']['properties'] + ['braketSchemaHeader']['const'], + } + # Unfortunatelly the Capabilities schemas are not homogeneus + # for real devices and simulators + elif result['deviceType'] == 'SIMULATOR': + deviceCapabilities = json.loads( + client.get_device(deviceArn=result['deviceArn']) + ['deviceCapabilities']) + self.backends[result['deviceName']] = { + 'nq': + deviceCapabilities['paradigm']['qubitCount'], + 'coupling_map': {}, + 'version': + deviceCapabilities['braketSchemaHeader']['version'], + 'location': + 'us-east-1', + 'deviceArn': + result['deviceArn'], + 'deviceParameters': + deviceCapabilities['deviceParameters']['properties'] + ['braketSchemaHeader']['const'], + 'deviceModelParameters': + deviceCapabilities['deviceParameters']['definitions'] + ['GateModelParameters']['properties'] + ['braketSchemaHeader']['const'], + } + + if verbose: + print('- List of AWSBraket devices available:') + print(list(self.backends)) + + return self.backends + + def is_online(self, device): + """ + Check if the device is in the list of available backends. + + Args: + device (str): name of the device to check + + Returns: + (bool) True if device is available, False otherwise + """ + # TODO: Add info for the device if it is actually ONLINE + 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 device to use + + 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'] + return nb_qubit_needed <= nb_qubit_max, nb_qubit_max, nb_qubit_needed + + def _run(self, info, device): + """ + Run the quantum code to the AWS Braket selected device. + + Args: + info (dict): dictionary sent by the backend containing the code to + run + device (str): name of the device to use + + Returns: + taskArn (str): The Arn of the task + + + """ + argument = { + 'circ': info['circuit'], + 's3_folder': self._s3_folder, + 'shots': info['shots'], + } + + region_name = self.backends[device]['location'] + device_parameters = { + 'braketSchemaHeader': self.backends[device]['deviceParameters'], + 'paradigmParameters': { + 'braketSchemaHeader': + self.backends[device]['deviceModelParameters'], + 'qubitCount': + info['nq'], + 'disableQubitRewiring': + False, + }, + } + device_parameters = json.dumps(device_parameters) + + client_braket = boto3.client( + 'braket', + region_name=region_name, + aws_access_key_id=self._credentials['AWS_ACCESS_KEY_ID'], + aws_secret_access_key=self._credentials['AWS_SECRET_KEY']) + + response = client_braket.create_quantum_task( + action=argument['circ'], + deviceArn=self.backends[device]['deviceArn'], + deviceParameters=device_parameters, + outputS3Bucket=argument['s3_folder'][0], + outputS3KeyPrefix=argument['s3_folder'][1], + shots=argument['shots']) + + return response['quantumTaskArn'] + + def _get_result(self, + execution_id, + num_retries=30, + interval=1, + verbose=False): + + if verbose: + print("Waiting for results. [Job Arn: {}]".format(execution_id)) + + original_sigint_handler = signal.getsignal(signal.SIGINT) + + def _handle_sigint_during_get_result(*_): # pragma: no cover + raise Exception( + "Interrupted. The Arn of your submitted job is {}.".format( + execution_id)) + + def _calculate_measurement_probs(measurements): + """ + Calculate the measurement probabilities based on the + list of measurements for a job sent to a SV1 Braket simulator + + Args: + measurements (list): list of measurements + + Returns: + measurementsProbabilities (dict): The measurements + with their probabilities + """ + total_mes = len(measurements) + unique_mes = [list(x) for x in set(tuple(x) for x in measurements)] + total_unique_mes = len(unique_mes) + len_qubits = len(unique_mes[0]) + measurements_probabilities = {} + for i in range(total_unique_mes): + strqubits = '' + for nq in range(len_qubits): + strqubits += str(unique_mes[i][nq]) + prob = measurements.count(unique_mes[i]) / total_mes + measurements_probabilities[strqubits] = prob + + return measurements_probabilities + + # The region_name is obtained from the taskArn itself + region_name = re.split(':', execution_id)[3] + client_braket = boto3.client( + 'braket', + region_name=region_name, + aws_access_key_id=self._credentials['AWS_ACCESS_KEY_ID'], + aws_secret_access_key=self._credentials['AWS_SECRET_KEY']) + + try: + signal.signal(signal.SIGINT, _handle_sigint_during_get_result) + + for _ in range(num_retries): + quantum_task = client_braket.get_quantum_task( + quantumTaskArn=execution_id) + status = quantum_task['status'] + bucket = quantum_task['outputS3Bucket'] + directory = quantum_task['outputS3Directory'] + resultsojectname = directory + '/results.json' + if status == 'COMPLETED': + # Get the device type to obtian the correct measurement + # structure + devicetype_used = client_braket.get_device( + deviceArn=quantum_task['deviceArn'])['deviceType'] + # Get the results from S3 + client_s3 = boto3.client('s3', + aws_access_key_id=self. + _credentials['AWS_ACCESS_KEY_ID'], + aws_secret_access_key=self. + _credentials['AWS_SECRET_KEY']) + s3result = client_s3.get_object(Bucket=bucket, + Key=resultsojectname) + if verbose: + print("Results obtained. [Status: {}]".format(status)) + result_content = json.loads(s3result['Body'].read()) + + if devicetype_used == 'QPU': + return result_content['measurementProbabilities'] + if devicetype_used == 'SIMULATOR': + return _calculate_measurement_probs( + result_content['measurements']) + if status == 'FAILED': + raise Exception("Error while running the code: {}. " + "The failure reason was: {}.".format( + status, quantum_task['failureReason'])) + if status == 'CANCELLING': + raise Exception("The job received a CANCEL " + "operation: {}.".format(status)) + time.sleep(interval) + # NOTE: Be aware that AWS is billing if a lot of API calls are + # executed, therefore the num_repetitions is set to a small + # number by default. + # For QPU devices the job is always queued and there are some + # working hours available. + # In addition the results and state is writen in the + # results.json file in the S3 Bucket and does not depend on the + # status of the device + + finally: + if original_sigint_handler is not None: + signal.signal(signal.SIGINT, original_sigint_handler) + + raise Exception("Timeout. " + "The Arn of your submitted job is {} and the status " + "of the job is {}.".format(execution_id, status)) + + +class DeviceTooSmall(Exception): + pass + + +class DeviceOfflineError(Exception): + pass + + +def show_devices(credentials=None, verbose=False): + """ + Access the list of available devices and their properties (ex: for setup + configuration) + + Args: + credentials (dict): Dictionary storing the AWS credentials with + keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + verbose (bool): If True, additional information is printed + + Returns: + (list) list of available devices and their properties + """ + awsbraket_session = AWSBraket() + awsbraket_session._authenticate(credentials=credentials) + return awsbraket_session.get_list_devices(verbose=verbose) + + +# TODO: Create a Show Online properties per device + + +def retrieve(credentials, taskArn, num_retries=30, interval=1, verbose=False): + """ + Retrieves a job/task by its Arn. + + Args: + credentials (dict): Dictionary storing the AWS credentials with + keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + taskArn (str): The Arn of the task to retreive + + Returns: + (dict) measurement probabilities from the result + stored in the S3 folder + """ + try: + awsbraket_session = AWSBraket() + if verbose: + print("- Authenticating...") + if credentials is not None: + print("AWS credentials: " + credentials['AWS_ACCESS_KEY_ID'] + + ", " + credentials['AWS_SECRET_KEY']) + awsbraket_session._authenticate(credentials=credentials) + res = awsbraket_session._get_result(taskArn, + num_retries=num_retries, + interval=interval, + verbose=verbose) + return res + except botocore.exceptions.ClientError as error: + error_code = error.response['Error']['Code'] + if error_code == 'ResourceNotFoundException': + print("- Unable to locate the job with Arn ", taskArn) + print(error, error_code) + raise + + +def send(info, + device, + credentials, + s3_folder, + num_retries=30, + interval=1, + verbose=False): + """ + Sends cicruit through the Boto3 SDK and runs the quantum circuit. + + Args: + info(dict): Contains representation of the circuit to run. + device (str): name of the AWS Braket device. + credentials (dict): Dictionary storing the AWS credentials with keys + AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + s3_folder (list): Contains the S3 bucket and directory to store the + results. + 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). + + Returns: + (list) samples from the AWS Braket device + + """ + try: + awsbraket_session = AWSBraket() + if verbose: + print("- Authenticating...") + if credentials is not None: + print("AWS credentials: " + credentials['AWS_ACCESS_KEY_ID'] + + ", " + credentials['AWS_SECRET_KEY']) + awsbraket_session._authenticate(credentials=credentials) + awsbraket_session._get_s3_folder(s3_folder=s3_folder) + + # check if the device is online/is available + awsbraket_session.get_list_devices(verbose) + online = awsbraket_session.is_online(device) + if online: + print("The job will be queued in any case, " + "plase take this into account") + else: + print("The device is not available. Use the " + "simulator instead or try another device.") + raise DeviceOfflineError("Device is not available.") + + # check if the device has enough qubit to run the code + runnable, qmax, qneeded = \ + awsbraket_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(info)) + taskArn = awsbraket_session._run(info, device) + print("Your task Arn is: {}. Make note of that for future reference". + format(taskArn)) + + if verbose: + print("- Waiting for results...") + res = awsbraket_session._get_result(taskArn, + num_retries=num_retries, + interval=interval, + verbose=verbose) + if verbose: + print("- Done.") + return res + + except botocore.exceptions.ClientError as error: + error_code = error.response['Error']['Code'] + if error_code == 'AccessDeniedException': + print("- There was an error: the access to Braket was denied") + if error_code == 'DeviceOfflineException': + print("- There was an error: the device is offline") + if error_code == 'InternalServiceException': + print("- There was an interal Bracket service error") + if error_code == 'ServiceQuotaExceededException': + print("- There was an error: the quota on Braket was exceed") + if error_code == 'ValidationException': + print("- There was a Validation error") + print(error, error_code) + raise diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py new file mode 100644 index 000000000..ba0d4062c --- /dev/null +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py @@ -0,0 +1,290 @@ +# Copyright 2021 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. +""" Test for projectq.backends._awsbraket._awsbraket_boto3_client.py """ + +import pytest +from unittest.mock import MagicMock, Mock, patch + +from io import StringIO + +import json + +from ._awsbraket_boto3_client_test_fixtures import * + +# ============================================================================== + +_has_boto3 = True +try: + from botocore.response import StreamingBody + import botocore + from projectq.backends._awsbraket import _awsbraket_boto3_client +except ImportError: + _has_boto3 = False + +has_boto3 = pytest.mark.skipif(not _has_boto3, + reason="boto3 package is not installed") + +# ============================================================================== + + +@has_boto3 +@patch('boto3.client') +def test_show_devices(mock_boto3_client, show_devices_setup): + creds, search_value, device_value, devicelist_result = show_devices_setup + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.search_devices.return_value = search_value + mock_boto3_client.get_device.return_value = device_value + + devicelist = _awsbraket_boto3_client.show_devices(credentials=creds) + assert devicelist == devicelist_result + + +# ============================================================================== + +completed_value = { + 'deviceArn': 'arndevice', + 'deviceParameters': 'parameters', + 'failureReason': 'None', + 'outputS3Bucket': 'amazon-braket-bucket', + 'outputS3Directory': 'complete/directory', + 'quantumTaskArn': 'arntask', + 'shots': 123, + 'status': 'COMPLETED', + 'tags': { + 'tagkey': 'tagvalue' + } +} + +failed_value = { + 'failureReason': 'This is a failure reason', + 'outputS3Bucket': 'amazon-braket-bucket', + 'outputS3Directory': 'complete/directory', + 'status': 'FAILED', +} + +cancelling_value = { + 'failureReason': 'None', + 'outputS3Bucket': 'amazon-braket-bucket', + 'outputS3Directory': 'complete/directory', + 'status': 'CANCELLING', +} + +other_value = { + 'failureReason': 'None', + 'outputS3Bucket': 'amazon-braket-bucket', + 'outputS3Directory': 'complete/directory', + 'status': 'OTHER', +} + +# ------------------------------------------------------------------------------ + + +@has_boto3 +@patch('boto3.client') +@pytest.mark.parametrize("var_status, var_result", + [('completed', completed_value), + ('failed', failed_value), + ('cancelling', cancelling_value), + ('other', other_value)]) +def test_retrieve(mock_boto3_client, var_status, var_result, retrieve_setup): + arntask, creds, device_value, res_completed, results_dict = retrieve_setup + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.get_quantum_task.return_value = var_result + mock_boto3_client.get_device.return_value = device_value + mock_boto3_client.get_object.return_value = results_dict + + if var_status == 'completed': + res = _awsbraket_boto3_client.retrieve(credentials=creds, + taskArn=arntask) + assert res == res_completed + else: + with pytest.raises(Exception) as exinfo: + _awsbraket_boto3_client.retrieve(credentials=creds, + taskArn=arntask, + num_retries=2) + print(exinfo.value) + if var_status == 'failed': + assert str(exinfo.value) == \ + "Error while running the code: FAILED. \ +The failure reason was: This is a failure reason." + + if var_status == 'cancelling': + assert str(exinfo.value) == \ + "The job received a CANCEL operation: CANCELLING." + if var_status == 'other': + assert str(exinfo.value) == \ + "Timeout. The Arn of your submitted job \ +is arn:aws:braket:us-east-1:id:taskuuid \ +and the status of the job is OTHER." + + +# ============================================================================== + + +@has_boto3 +@patch('boto3.client') +def test_retrieve_devicetypes(mock_boto3_client, retrieve_devicetypes_setup): + (arntask, creds, device_value, results_dict, + res_completed) = retrieve_devicetypes_setup + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.get_quantum_task.return_value = completed_value + mock_boto3_client.get_device.return_value = device_value + mock_boto3_client.get_object.return_value = results_dict + + res = _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask) + assert res == res_completed + + +# ============================================================================== + + +@has_boto3 +@patch('boto3.client') +def test_send_too_many_qubits(mock_boto3_client, send_too_many_setup): + (creds, s3_folder, search_value, device_value, + info_too_much) = send_too_many_setup + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.search_devices.return_value = search_value + mock_boto3_client.get_device.return_value = device_value + + with pytest.raises(_awsbraket_boto3_client.DeviceTooSmall): + _awsbraket_boto3_client.send(info_too_much, + device='name2', + credentials=creds, + s3_folder=s3_folder) + + +# ============================================================================== + + +@has_boto3 +@patch('boto3.client') +@pytest.mark.parametrize("var_status, var_result", + [('completed', completed_value), + ('failed', failed_value), + ('cancelling', cancelling_value), + ('other', other_value)]) +def test_send_real_device_online_verbose(mock_boto3_client, var_status, + var_result, real_device_online_setup): + + (qtarntask, creds, s3_folder, info, search_value, device_value, + res_completed, results_dict) = real_device_online_setup + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.search_devices.return_value = search_value + mock_boto3_client.get_device.return_value = device_value + mock_boto3_client.create_quantum_task.return_value = qtarntask + mock_boto3_client.get_quantum_task.return_value = var_result + mock_boto3_client.get_object.return_value = results_dict + + # This is a ficticios situation because the job will be always queued + # at the beginning. After that the status will change at some point in time + # If the status change while the _get_result loop with num_retries, is + # active the result will change. We mock this using some preconfigured + # statuses in var_status for the tests + + if var_status == 'completed': + res = _awsbraket_boto3_client.send(info, + device='name2', + credentials=creds, + s3_folder=s3_folder, + verbose=True) + assert res == res_completed + else: + with pytest.raises(Exception) as exinfo: + _awsbraket_boto3_client.send(info, + device='name2', + credentials=creds, + s3_folder=s3_folder, + verbose=True, + num_retries=2) + print(exinfo.value) + if var_status == 'failed': + assert str(exinfo.value) == \ + "Error while running the code: FAILED. The failure \ +reason was: This is a failure reason." + + if var_status == 'cancelling': + assert str(exinfo.value) == \ + "The job received a CANCEL operation: CANCELLING." + if var_status == 'other': + assert str(exinfo.value) == \ + "Timeout. The Arn of your submitted job \ +is arn:aws:braket:us-east-1:id:taskuuid \ +and the status of the job is OTHER." + + +# ============================================================================== + + +@has_boto3 +@patch('boto3.client') +@pytest.mark.parametrize("var_error", [('AccessDeniedException'), + ('DeviceOfflineException'), + ('InternalServiceException'), + ('ServiceQuotaExceededException'), + ('ValidationException')]) +def test_send_that_errors_are_caught(mock_boto3_client, var_error, + send_that_error_setup): + creds, s3_folder, info, search_value, device_value = send_that_error_setup + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.search_devices.return_value = search_value + mock_boto3_client.get_device.return_value = device_value + mock_boto3_client.create_quantum_task.side_effect = \ + botocore.exceptions.ClientError( + {"Error": { + "Code": var_error, + "Message": "Msg error for "+var_error}}, "create_quantum_task") + + with pytest.raises(botocore.exceptions.ClientError) as exinfo: + _awsbraket_boto3_client.send(info, + device='name2', + credentials=creds, + s3_folder=s3_folder, + num_retries=2) + + with pytest.raises(_awsbraket_boto3_client.DeviceOfflineError) as exinfo: + _awsbraket_boto3_client.send(info, + device='unknown', + credentials=creds, + s3_folder=s3_folder, + num_retries=2) + + +# ============================================================================== + + +@has_boto3 +@patch('boto3.client') +@pytest.mark.parametrize("var_error", [('ResourceNotFoundException')]) +def test_retrieve_error_arn_not_exist(mock_boto3_client, var_error, arntask, + creds): + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.get_quantum_task.side_effect = \ + botocore.exceptions.ClientError( + {"Error": { + "Code": var_error, + "Message": "Msg error for "+var_error}}, "get_quantum_task") + + with pytest.raises(botocore.exceptions.ClientError) as exinfo: + _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask) + + +# ============================================================================== diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py new file mode 100644 index 000000000..a46cc7c77 --- /dev/null +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py @@ -0,0 +1,407 @@ +# Copyright 2021 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. +# +# ============================================================================== +# This file contains: +# +# - Helper fixtures: +# * arntask +# * creds +# * s3_folder +# * info +# * results_json +# * results_dict +# * res_completed +# * search_value +# * device_value +# * devicelist_result +# - Setup fixtures for specific tests: +# * show_devices_setup +# * retrieve_setup +# * retrieve_devicetypes_setup +# * send_too_many_setup +# * real_device_online_setup +# ============================================================================== + +from io import StringIO +import json +import pytest + +try: + from botocore.response import StreamingBody +except ImportError: + + class StreamingBody: + def __init__(self, d, l): + pass + + +# ============================================================================== + + +@pytest.fixture +def arntask(): + return 'arn:aws:braket:us-east-1:id:taskuuid' + + +@pytest.fixture +def creds(): + return { + 'AWS_ACCESS_KEY_ID': 'aws_access_key_id', + 'AWS_SECRET_KEY': 'aws_secret_key', + } + + +@pytest.fixture +def s3_folder(): + return ['S3Bucket', "S3Directory"] + + +@pytest.fixture +def info(): + return { + 'circuit': + '{"braketSchemaHeader":' + '{"name": "braket.ir.jaqcd.program", "version": "1"}, ' + '"results": [], "basis_rotation_instructions": [], ' + '"instructions": [{"target": 0, "type": "h"}, {\ + "target": 1, "type": "h"}, {\ + "control": 1, "target": 2, "type": "cnot"}]}', + 'nq': + 10, + 'shots': + 1, + 'backend': { + 'name': 'name2' + } + } + + +@pytest.fixture +def results_json(): + return json.dumps({ + "braketSchemaHeader": { + "name": "braket.task_result.gate_model_task_result", + "version": "1" + }, + "measurementProbabilities": { + "000": 0.1, + "010": 0.4, + "110": 0.1, + "001": 0.1, + "111": 0.3 + }, + "measuredQubits": [0, 1, 2], + }) + + +@pytest.fixture +def results_dict(results_json): + body = StreamingBody(StringIO(results_json), len(results_json)) + return { + 'ResponseMetadata': { + 'RequestId': 'CF4CAA48CC18836C', + 'HTTPHeaders': {}, + }, + 'Body': body + } + + +@pytest.fixture +def res_completed(): + return {"000": 0.1, "010": 0.4, "110": 0.1, "001": 0.1, "111": 0.3} + + +@pytest.fixture +def search_value(): + return { + "devices": [ + { + "deviceArn": "arn1", + "deviceName": "name1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn2", + "deviceName": "name2", + "deviceType": "QPU", + "deviceStatus": "OFFLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn3", + "deviceName": "name3", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "pname2", + }, + { + "deviceArn": "invalid", + "deviceName": "invalid", + "deviceType": "BLABLA", + "deviceStatus": "ONLINE", + "providerName": "pname3", + }, + ] + } + + +@pytest.fixture +def device_value_devicecapabilities(): + return json.dumps({ + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [{ + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + }], + "shotsRange": [1, 10], + "deviceLocation": + "us-east-1", + }, + "action": { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": { + "qubitCount": 30, + "nativeGateSet": ["ccnot", "cy"], + "connectivity": { + "fullyConnected": False, + "connectivityGraph": { + "1": ["2", "3"] + } + }, + }, + "deviceParameters": { + "properties": { + "braketSchemaHeader": { + "const": { + "name": + "braket.device_schema.rigetti.rigetti_device_parameters", + "version": "1" + } + } + }, + "definitions": { + "GateModelParameters": { + "properties": { + "braketSchemaHeader": { + "const": { + "name": + "braket.device_schema.gate_model_parameters", + "version": "1" + } + } + } + } + }, + }, + }) + + +@pytest.fixture +def device_value(device_value_devicecapabilities): + return { + "deviceName": "Aspen-8", + "deviceType": "QPU", + "providerName": "provider1", + "deviceStatus": "OFFLINE", + "deviceCapabilities": device_value_devicecapabilities, + } + + +@pytest.fixture +def devicelist_result(): + return { + 'name1': { + 'coupling_map': {}, + 'deviceArn': 'arn1', + 'location': 'us-east-1', + 'nq': 30, + 'version': '1', + 'deviceParameters': { + 'name': + 'braket.device_schema.rigetti.rigetti_device_parameters', + 'version': '1' + }, + 'deviceModelParameters': { + 'name': 'braket.device_schema.gate_model_parameters', + 'version': '1' + } + }, + 'name2': { + 'coupling_map': { + '1': ['2', '3'] + }, + 'deviceArn': 'arn2', + 'location': 'us-east-1', + 'nq': 30, + 'version': '1', + 'deviceParameters': { + 'name': + 'braket.device_schema.rigetti.rigetti_device_parameters', + 'version': '1' + }, + 'deviceModelParameters': { + 'name': 'braket.device_schema.gate_model_parameters', + 'version': '1' + } + }, + 'name3': { + 'coupling_map': { + '1': ['2', '3'] + }, + 'deviceArn': 'arn3', + 'location': 'us-east-1', + 'nq': 30, + 'version': '1', + 'deviceParameters': { + 'name': + 'braket.device_schema.rigetti.rigetti_device_parameters', + 'version': '1' + }, + 'deviceModelParameters': { + 'name': 'braket.device_schema.gate_model_parameters', + 'version': '1' + } + } + } + + +# ============================================================================== + + +@pytest.fixture +def show_devices_setup(creds, search_value, device_value, devicelist_result): + return creds, search_value, device_value, devicelist_result + + +@pytest.fixture +def retrieve_setup(arntask, creds, device_value, res_completed, results_dict): + return arntask, creds, device_value, res_completed, results_dict + + +@pytest.fixture(params=["qpu", "sim"]) +def retrieve_devicetypes_setup(request, arntask, creds, results_json, + device_value_devicecapabilities): + if request.param == "qpu": + body_qpu = StreamingBody(StringIO(results_json), len(results_json)) + results_dict = { + 'ResponseMetadata': { + 'RequestId': 'CF4CAA48CC18836C', + 'HTTPHeaders': {}, + }, + 'Body': body_qpu + } + + device_value = { + "deviceName": "Aspen-8", + "deviceType": "QPU", + "providerName": "provider1", + "deviceStatus": "OFFLINE", + "deviceCapabilities": device_value_devicecapabilities, + } + + res_completed = { + "000": 0.1, + "010": 0.4, + "110": 0.1, + "001": 0.1, + "111": 0.3 + } + else: + results_json_simulator = json.dumps({ + "braketSchemaHeader": { + "name": "braket.task_result.gate_model_task_result", + "version": "1" + }, + "measurements": [[0, 0], [0, 1], [1, 1], [0, 1], [0, 1], [1, 1], + [1, 1], [1, 1], [1, 1], [1, 1]], + "measuredQubits": [0, 1], + }) + body_simulator = \ + StreamingBody( + StringIO(results_json_simulator), len( + results_json_simulator)) + results_dict = { + 'ResponseMetadata': { + 'RequestId': 'CF4CAA48CC18836C', + 'HTTPHeaders': {}, + }, + 'Body': body_simulator + } + + device_value = { + "deviceName": "SV1", + "deviceType": "SIMULATOR", + "providerName": "providerA", + "deviceStatus": "ONLINE", + "deviceCapabilities": device_value_devicecapabilities, + } + + res_completed = {"00": 0.1, "01": 0.3, "11": 0.6} + return arntask, creds, device_value, results_dict, res_completed + + +@pytest.fixture +def send_too_many_setup(creds, s3_folder, search_value, device_value): + info_too_much = { + 'circuit': + '{"braketSchemaHeader":' + '{"name": "braket.ir.jaqcd.program", "version": "1"}, ' + '"results": [], "basis_rotation_instructions": [], ' + '"instructions": [{"target": 0, "type": "h"}, {\ + "target": 1, "type": "h"}, {\ + "control": 1, "target": 2, "type": "cnot"}]}', + 'nq': + 100, + 'shots': + 1, + 'backend': { + 'name': 'name2' + } + } + return creds, s3_folder, search_value, device_value, info_too_much + + +@pytest.fixture +def real_device_online_setup(arntask, creds, s3_folder, info, search_value, + device_value, res_completed, results_json): + qtarntask = {'quantumTaskArn': arntask} + body = StreamingBody(StringIO(results_json), len(results_json)) + results_dict = { + 'ResponseMetadata': { + 'RequestId': 'CF4CAA48CC18836C', + 'HTTPHeaders': {}, + }, + 'Body': body + } + + return (qtarntask, creds, s3_folder, info, search_value, device_value, + res_completed, results_dict) + + +@pytest.fixture +def send_that_error_setup(creds, s3_folder, info, search_value, device_value): + return creds, s3_folder, info, search_value, device_value diff --git a/projectq/backends/_awsbraket/_awsbraket_test.py b/projectq/backends/_awsbraket/_awsbraket_test.py new file mode 100644 index 000000000..dcb33f515 --- /dev/null +++ b/projectq/backends/_awsbraket/_awsbraket_test.py @@ -0,0 +1,563 @@ +# Copyright 2021 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. +""" Test for projectq.backends._awsbraket._awsbraket.py""" + +import pytest +from unittest.mock import MagicMock, Mock, patch + +import copy +import math +from projectq.setups import restrictedgateset +from projectq import MainEngine + +from projectq.types import WeakQubitRef, Qubit +from projectq.cengines import (BasicMapperEngine, DummyEngine, AutoReplacer, + DecompositionRuleSet) +from projectq.cengines._replacer import NoGateDecompositionError + +from projectq.ops import (R, Swap, H, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z, + CNOT, SqrtX, MatrixGate, Entangle, Ph, NOT, C, + Measure, Allocate, Deallocate, Barrier, All, Command) + +from ._awsbraket_test_fixtures import * + +# ============================================================================== + +_has_boto3 = True +try: + from botocore.response import StreamingBody + import botocore + from projectq.backends._awsbraket import _awsbraket +except ImportError: + _has_boto3 = False + +has_boto3 = pytest.mark.skipif(not _has_boto3, + reason="boto3 package is not installed") + +# ============================================================================== + + +@pytest.fixture(params=["mapper", "no_mapper"]) +def mapper(request): + """ + Adds a mapper which changes qubit ids by adding 1 + """ + if request.param == "mapper": + + class TrivialMapper(BasicMapperEngine): + def __init__(self): + super().__init__() + self.current_mapping = dict() + + def receive(self, command_list): + for cmd in command_list: + for qureg in cmd.all_qubits: + for qubit in qureg: + if qubit.id == -1: + continue + elif qubit.id not in self.current_mapping: + previous_map = self.current_mapping + previous_map[qubit.id] = qubit.id + self.current_mapping = previous_map + self._send_cmd_with_mapped_ids(cmd) + + return TrivialMapper() + if request.param == "no_mapper": + return None + + +# ============================================================================== +''' +Gate availability Tests +''' + + +@has_boto3 +@pytest.mark.parametrize("single_qubit_gate_aspen, is_available_aspen", + [(X, True), (Y, True), (Z, True), (H, True), + (T, True), (Tdag, True), (S, True), (Sdag, True), + (Allocate, True), (Deallocate, True), (SqrtX, False), + (Measure, True), (Rx(0.5), True), (Ry(0.5), True), + (Rz(0.5), True), (Ph(0.5), False), (R(0.5), True), + (Barrier, True), (Entangle, False)]) +def test_awsbraket_backend_is_available_aspen(single_qubit_gate_aspen, + is_available_aspen): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, + device='Aspen-8') + cmd = Command(eng, single_qubit_gate_aspen, (qubit1, )) + assert aws_backend.is_available(cmd) == is_available_aspen + + +@has_boto3 +@pytest.mark.parametrize("single_qubit_gate_ionq, is_available_ionq", + [(X, True), (Y, True), (Z, True), (H, True), + (T, True), (Tdag, True), (S, True), (Sdag, True), + (Allocate, True), (Deallocate, True), (SqrtX, True), + (Measure, True), (Rx(0.5), True), (Ry(0.5), True), + (Rz(0.5), True), (Ph(0.5), False), (R(0.5), False), + (Barrier, True), (Entangle, False)]) +def test_awsbraket_backend_is_available_ionq(single_qubit_gate_ionq, + is_available_ionq): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, + device='IonQ Device') + cmd = Command(eng, single_qubit_gate_ionq, (qubit1, )) + assert aws_backend.is_available(cmd) == is_available_ionq + + +@has_boto3 +@pytest.mark.parametrize( + "single_qubit_gate_sv1, is_available_sv1", + [ + (X, True), + (Y, True), + (Z, True), + (H, True), + (T, True), + (Tdag, True), + (S, True), + (Sdag, True), + (Allocate, True), + (Deallocate, True), + (SqrtX, True), + (Measure, True), + (Rx(0.5), True), + # use MatrixGate as unitary gate + (MatrixGate([[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0] + ]), False), + (Ry(0.5), True), + (Rz(0.5), True), + (Ph(0.5), False), + (R(0.5), True), + (Barrier, True), + (Entangle, False) + ]) +def test_awsbraket_backend_is_available_sv1(single_qubit_gate_sv1, + is_available_sv1): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=False) + cmd = Command(eng, single_qubit_gate_sv1, (qubit1, )) + assert aws_backend.is_available(cmd) == is_available_sv1 + + +@has_boto3 +@pytest.mark.parametrize("num_ctrl_qubits_aspen, is_available_aspen", + [(0, True), (1, True), (2, True), (3, False)]) +def test_awsbraket_backend_is_available_control_not_aspen( + num_ctrl_qubits_aspen, is_available_aspen): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits_aspen) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, + device='Aspen-8') + cmd = Command(eng, X, (qubit1, ), controls=qureg) + assert aws_backend.is_available(cmd) == is_available_aspen + + +@has_boto3 +@pytest.mark.parametrize("num_ctrl_qubits_ionq, is_available_ionq", + [(0, True), (1, True), (2, False), (3, False)]) +def test_awsbraket_backend_is_available_control_not_ionq( + num_ctrl_qubits_ionq, is_available_ionq): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits_ionq) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, + device='IonQ Device') + cmd = Command(eng, X, (qubit1, ), controls=qureg) + assert aws_backend.is_available(cmd) == is_available_ionq + + +@has_boto3 +@pytest.mark.parametrize("num_ctrl_qubits_sv1, is_available_sv1", [(0, True), + (1, True), + (2, True), + (3, False)]) +def test_awsbraket_backend_is_available_control_not_sv1( + num_ctrl_qubits_sv1, is_available_sv1): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + qureg = eng.allocate_qureg(num_ctrl_qubits_sv1) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=False) + cmd = Command(eng, X, (qubit1, ), controls=qureg) + assert aws_backend.is_available(cmd) == is_available_sv1 + + +@has_boto3 +@pytest.mark.parametrize("ctrl_singlequbit_aspen, is_available_aspen", + [(X, True), (Y, False), (Z, True), (R(0.5), True), + (Rx(0.5), False), (Ry(0.5), False), (Rz(0.5), False), + (NOT, True)]) +def test_awsbraket_backend_is_available_control_singlequbit_aspen( + ctrl_singlequbit_aspen, is_available_aspen): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + qureg = eng.allocate_qureg(1) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, + device='Aspen-8') + cmd = Command(eng, ctrl_singlequbit_aspen, (qubit1, ), controls=qureg) + assert aws_backend.is_available(cmd) == is_available_aspen + + +@has_boto3 +@pytest.mark.parametrize("ctrl_singlequbit_ionq, is_available_ionq", + [(X, True), (Y, False), (Z, False), (R(0.5), False), + (Rx(0.5), False), (Ry(0.5), False), + (Rz(0.5), False)]) +def test_awsbraket_backend_is_available_control_singlequbit_ionq( + ctrl_singlequbit_ionq, is_available_ionq): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + qureg = eng.allocate_qureg(1) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, + device='IonQ Device') + cmd = Command(eng, ctrl_singlequbit_ionq, (qubit1, ), controls=qureg) + assert aws_backend.is_available(cmd) == is_available_ionq + + +@has_boto3 +@pytest.mark.parametrize("ctrl_singlequbit_sv1, is_available_sv1", + [(X, True), (Y, True), (Z, True), (R(0.5), True), + (Rx(0.5), False), (Ry(0.5), False), + (Rz(0.5), False)]) +def test_awsbraket_backend_is_available_control_singlequbit_sv1( + ctrl_singlequbit_sv1, is_available_sv1): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + qureg = eng.allocate_qureg(1) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=False) + cmd = Command(eng, ctrl_singlequbit_sv1, (qubit1, ), controls=qureg) + assert aws_backend.is_available(cmd) == is_available_sv1 + + +@has_boto3 +def test_awsbraket_backend_is_available_swap_aspen(): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, + device='Aspen-8') + cmd = Command(eng, Swap, (qubit1, qubit2)) + assert aws_backend.is_available(cmd) == True + + +@has_boto3 +def test_awsbraket_backend_is_available_swap_ionq(): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, + device='IonQ Device') + cmd = Command(eng, Swap, (qubit1, qubit2)) + assert aws_backend.is_available(cmd) == True + + +@has_boto3 +def test_awsbraket_backend_is_available_swap_sv1(): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=False) + cmd = Command(eng, Swap, (qubit1, qubit2)) + assert aws_backend.is_available(cmd) == True + + +@has_boto3 +def test_awsbraket_backend_is_available_control_swap_aspen(): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + qureg = eng.allocate_qureg(1) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, + device='Aspen-8') + cmd = Command(eng, Swap, (qubit1, qubit2), controls=qureg) + assert aws_backend.is_available(cmd) == True + + +@has_boto3 +def test_awsbraket_backend_is_available_control_swap_sv1(): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + qureg = eng.allocate_qureg(1) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=False) + cmd = Command(eng, Swap, (qubit1, qubit2), controls=qureg) + assert aws_backend.is_available(cmd) == True + + +''' +End of Gate availability Tests +''' + + +@has_boto3 +def test_awsbraket_backend_init(): + backend = _awsbraket.AWSBraketBackend(verbose=True, use_hardware=True) + assert len(backend._circuit) == 0 + + +@has_boto3 +def test_awsbraket_empty_circuit(): + backend = _awsbraket.AWSBraketBackend(verbose=True) + eng = MainEngine(backend=backend) + eng.flush() + + +@has_boto3 +def test_awsbraket_invalid_command(): + backend = _awsbraket.AWSBraketBackend(use_hardware=True, verbose=True) + qb = WeakQubitRef(None, 1) + cmd = Command(None, gate=SqrtX, qubits=[(qb, )]) + with pytest.raises(Exception) as excinfo: + backend.receive([cmd]) + + +# ============================================================================== + + +@has_boto3 +@patch('boto3.client') +def test_awsbraket_sent_error(mock_boto3_client, sent_error_setup): + creds, s3_folder, search_value, device_value = sent_error_setup + + var_error = 'ServiceQuotaExceededException' + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.search_devices.return_value = search_value + mock_boto3_client.get_device.return_value = device_value + mock_boto3_client.create_quantum_task.side_effect = \ + botocore.exceptions.ClientError( + {"Error": {"Code": var_error, + "Message": "Msg error for "+var_error}}, + "create_quantum_task" + ) + + backend = _awsbraket.AWSBraketBackend(verbose=True, + credentials=creds, + s3_folder=s3_folder, + use_hardware=True, + device='Aspen-8', + num_runs=10) + eng = MainEngine(backend=backend, verbose=True) + qubit = eng.allocate_qubit() + Rx(0.5) | qubit + qubit[0].__del__() + with pytest.raises(botocore.exceptions.ClientError): + eng.flush() + + # atexit sends another FlushGate, therefore we remove the backend: + dummy = DummyEngine() + dummy.is_last_engine = True + eng.next_engine = dummy + + +@has_boto3 +def test_awsbraket_sent_error_2(): + backend = _awsbraket.AWSBraketBackend(verbose=True, + use_hardware=True, + device='Aspen-8') + eng = MainEngine( + backend=backend, + engine_list=[AutoReplacer(DecompositionRuleSet())], + verbose=True) + qubit = eng.allocate_qubit() + Rx(math.pi) | qubit + + with pytest.raises(NoGateDecompositionError): + SqrtX | qubit + # no setup to decompose SqrtX gate for Aspen-8, + # so not accepted by the backend + dummy = DummyEngine() + dummy.is_last_engine = True + eng.next_engine = dummy + + +# ============================================================================== + + +@has_boto3 +@patch('boto3.client') +def test_awsbraket_retrieve(mock_boto3_client, retrieve_setup): + (arntask, creds, completed_value, device_value, + results_dict) = retrieve_setup + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.get_quantum_task.return_value = completed_value + mock_boto3_client.get_device.return_value = device_value + mock_boto3_client.get_object.return_value = results_dict + + backend = _awsbraket.AWSBraketBackend(retrieve_execution=arntask, + credentials=creds, + num_retries=2, + 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], verbose=True) + + separate_qubit = eng.allocate_qubit() + qureg = eng.allocate_qureg(3) + del separate_qubit + eng.flush() + + prob_dict = eng.backend.get_probabilities([qureg[0], qureg[2], qureg[1]]) + assert prob_dict['000'] == 0.04 + assert prob_dict['101'] == 0.2 + assert prob_dict['010'] == 0.8 + + # Unknown qubit or no mapper + invalid_qubit = [Qubit(eng, 10)] + with pytest.raises(RuntimeError): + eng.backend.get_probabilities(invalid_qubit) + + +# ============================================================================== + + +@has_boto3 +@patch('boto3.client') +def test_awsbraket_backend_functional_test(mock_boto3_client, functional_setup, + mapper): + (creds, s3_folder, search_value, device_value, qtarntask, completed_value, + results_dict) = functional_setup + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.search_devices.return_value = search_value + mock_boto3_client.get_device.return_value = device_value + mock_boto3_client.create_quantum_task.return_value = qtarntask + mock_boto3_client.get_quantum_task.return_value = completed_value + mock_boto3_client.get_object.return_value = results_dict + + backend = _awsbraket.AWSBraketBackend(verbose=True, + credentials=creds, + s3_folder=s3_folder, + use_hardware=True, + device='Aspen-8', + num_runs=10, + num_retries=2) + # no circuit has been executed -> raises exception + with pytest.raises(RuntimeError): + backend.get_probabilities([]) + + from projectq.setups.default import get_engine_list + from projectq.backends import CommandPrinter, ResourceCounter + + rcount = ResourceCounter() + engine_list = [rcount] + if mapper is not None: + engine_list.append(mapper) + eng = MainEngine(backend=backend, engine_list=engine_list, verbose=True) + + unused_qubit = eng.allocate_qubit() + qureg = eng.allocate_qureg(3) + + H | qureg[0] + S | qureg[1] + T | qureg[2] + NOT | qureg[0] + Y | qureg[1] + Z | qureg[2] + Rx(0.1) | qureg[0] + Ry(0.2) | qureg[1] + Rz(0.3) | qureg[2] + R(0.6) | qureg[2] + C(X) | (qureg[1], qureg[2]) + C(Swap) | (qureg[0], qureg[1], qureg[2]) + H | qureg[0] + C(Z) | (qureg[1], qureg[2]) + C(R(0.5)) | (qureg[1], qureg[0]) + C(NOT, 2) | ([qureg[2], qureg[1]], qureg[0]) + Swap | (qureg[2], qureg[0]) + Tdag | qureg[1] + Sdag | qureg[0] + + All(Barrier) | qureg + del unused_qubit + # measure; should be all-0 or all-1 + All(Measure) | qureg + # run the circuit + eng.flush() + + prob_dict = eng.backend.get_probabilities([qureg[0], qureg[1]]) + assert prob_dict['00'] == pytest.approx(0.84) + assert prob_dict['01'] == pytest.approx(0.06) + + +@has_boto3 +@patch('boto3.client') +def test_awsbraket_functional_test_as_engine(mock_boto3_client, + functional_setup): + (creds, s3_folder, search_value, device_value, qtarntask, completed_value, + results_dict) = functional_setup + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.search_devices.return_value = search_value + mock_boto3_client.get_device.return_value = device_value + mock_boto3_client.create_quantum_task.return_value = qtarntask + mock_boto3_client.get_quantum_task.return_value = completed_value + mock_boto3_client.get_object.return_value = copy.deepcopy(results_dict) + + backend = _awsbraket.AWSBraketBackend(verbose=True, + credentials=creds, + s3_folder=s3_folder, + use_hardware=True, + device='Aspen-8', + num_runs=10, + num_retries=2) + # no circuit has been executed -> raises exception + with pytest.raises(RuntimeError): + backend.get_probabilities([]) + + from projectq.setups.default import get_engine_list + from projectq.backends import CommandPrinter, ResourceCounter + + eng = MainEngine(backend=DummyEngine(save_commands=True), + engine_list=[backend], + verbose=True) + + unused_qubit = eng.allocate_qubit() + qureg = eng.allocate_qureg(3) + + H | qureg[0] + CNOT | (qureg[0], qureg[1]) + eng.flush() + + assert len(eng.backend.received_commands) == 7 + assert eng.backend.received_commands[4].gate == H + assert eng.backend.received_commands[4].qubits[0][0].id == qureg[0].id + assert eng.backend.received_commands[5].gate == X + assert eng.backend.received_commands[5].control_qubits[0].id == qureg[0].id + assert eng.backend.received_commands[5].qubits[0][0].id == qureg[1].id + + # NB: also test that we can call eng.flush() multiple times + + mock_boto3_client.get_object.return_value = copy.deepcopy(results_dict) + + CNOT | (qureg[1], qureg[0]) + H | qureg[1] + eng.flush() + + assert len(eng.backend.received_commands) == 10 + assert eng.backend.received_commands[7].gate == X + assert eng.backend.received_commands[7].control_qubits[0].id == qureg[1].id + assert eng.backend.received_commands[7].qubits[0][0].id == qureg[0].id + assert eng.backend.received_commands[8].gate == H + assert eng.backend.received_commands[8].qubits[0][0].id == qureg[1].id diff --git a/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py new file mode 100644 index 000000000..248d1d7d9 --- /dev/null +++ b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py @@ -0,0 +1,240 @@ +# Copyright 2021 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. +# +# ============================================================================== +# This file contains: +# +# - Helper fixtures: +# * arntask +# * creds +# * s3_folder +# * device_value +# * search_value +# * completed_value +# - Setup fixtures for specific tests: +# * sent_error_setup +# * retrieve_setup +# * functional_setup +# ============================================================================== + +from io import StringIO +import json +import pytest + +try: + from botocore.response import StreamingBody +except ImportError: + + class StreamingBody: + def __init__(self, d, l): + pass + + +# ============================================================================== + + +@pytest.fixture +def arntask(): + return 'arn:aws:braket:us-east-1:id:retrieve_execution' + + +@pytest.fixture +def creds(): + return { + 'AWS_ACCESS_KEY_ID': 'aws_access_key_id', + 'AWS_SECRET_KEY': 'aws_secret_key', + } + + +@pytest.fixture +def s3_folder(): + return ['S3Bucket', 'S3Directory'] + + +@pytest.fixture +def device_value(): + device_value_devicecapabilities = json.dumps({ + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [{ + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + }], + "shotsRange": [1, 10], + "deviceLocation": + "us-east-1", + }, + "action": { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": { + "qubitCount": 30, + "nativeGateSet": ["ccnot", "cy"], + "connectivity": { + "fullyConnected": False, + "connectivityGraph": { + "1": ["2", "3"] + } + }, + }, + "deviceParameters": { + "properties": { + "braketSchemaHeader": { + "const": { + "name": + "braket.device_schema.rigetti.rigetti_device_parameters", + "version": "1" + } + } + }, + "definitions": { + "GateModelParameters": { + "properties": { + "braketSchemaHeader": { + "const": { + "name": + "braket.device_schema.gate_model_parameters", + "version": "1" + } + } + } + } + }, + }, + }) + + return { + "deviceName": "Aspen-8", + "deviceType": "QPU", + "providerName": "provider1", + "deviceStatus": "OFFLINE", + "deviceCapabilities": device_value_devicecapabilities, + } + + +@pytest.fixture +def search_value(): + return { + "devices": [ + { + "deviceArn": "arn1", + "deviceName": "name1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn2", + "deviceName": "name2", + "deviceType": "QPU", + "deviceStatus": "OFFLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn3", + "deviceName": "Aspen-8", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "pname2", + }, + ] + } + + +@pytest.fixture +def completed_value(): + return { + 'deviceArn': 'arndevice', + 'deviceParameters': 'parameters', + 'outputS3Bucket': 'amazon-braket-bucket', + 'outputS3Directory': 'complete/directory', + 'quantumTaskArn': 'arntask', + 'shots': 123, + 'status': 'COMPLETED', + 'tags': { + 'tagkey': 'tagvalue' + } + } + + +# ============================================================================== + + +@pytest.fixture +def sent_error_setup(creds, s3_folder, device_value, search_value): + + return creds, s3_folder, search_value, device_value + + +@pytest.fixture +def results_json(): + return json.dumps({ + "braketSchemaHeader": { + "name": "braket.task_result.gate_model_task_result", + "version": "1" + }, + "measurementProbabilities": { + "0000": 0.04, + "0010": 0.06, + "0110": 0.2, + "0001": 0.3, + "1001": 0.5 + }, + "measuredQubits": [0, 1, 2], + }) + + +@pytest.fixture +def retrieve_setup(arntask, creds, device_value, completed_value, + results_json): + + body = StreamingBody(StringIO(results_json), len(results_json)) + + results_dict = { + 'ResponseMetadata': { + 'RequestId': 'CF4CAA48CC18836C', + 'HTTPHeaders': {}, + }, + 'Body': body + } + + return arntask, creds, completed_value, device_value, results_dict + + +@pytest.fixture +def functional_setup(arntask, creds, s3_folder, search_value, device_value, + completed_value, results_json): + qtarntask = {'quantumTaskArn': arntask} + body2 = StreamingBody(StringIO(results_json), len(results_json)) + results_dict = { + 'ResponseMetadata': { + 'RequestId': 'CF4CAA48CC18836C', + 'HTTPHeaders': {}, + }, + 'Body': body2 + } + + return (creds, s3_folder, search_value, device_value, qtarntask, + completed_value, results_dict) + + +# ============================================================================== diff --git a/projectq/setups/awsbraket.py b/projectq/setups/awsbraket.py new file mode 100644 index 000000000..c9bd1b289 --- /dev/null +++ b/projectq/setups/awsbraket.py @@ -0,0 +1,75 @@ +# Copyright 2021 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 allowing to compile code for the AWS Braket devices: +->The 11 qubits IonQ device +->The 32 qubits Rigetti device +->The up to 34 qubits SV1 state vector simulator + +It provides the `engine_list` for the `MainEngine' based on the requested +device. Decompose the circuit into the available gate set for each device +that will be used in the backend. +""" + +import projectq +import projectq.setups.decompositions +from projectq.setups import restrictedgateset +from projectq.ops import (R, Swap, H, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z, + SqrtX, Barrier) +from projectq.cengines import (LocalOptimizer, IBM5QubitMapper, + SwapAndCNOTFlipper, BasicMapperEngine, + GridMapper) +from projectq.backends._awsbraket._awsbraket_boto3_client import show_devices + + +def get_engine_list(credentials=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(credentials) + if device not in devices: + raise DeviceOfflineError('Error when configuring engine list: device ' + 'requested for Backend not available') + + # We left the real device to manage the mapping and optimizacion: "The IonQ + # and Rigetti devices compile the provided circuit into their respective + # native gate sets automatically, and they map the abstract qubit indices + # to physical qubits on the respective QPU." + # (see: https://docs.aws.amazon.com/braket/latest/developerguide/braket-submit-to-qpu.html) + + # TODO: Investigate if explicit mapping is an advantage + + if device == 'SV1': + setup = restrictedgateset.get_engine_list( + one_qubit_gates=(R, H, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z, + SqrtX), + two_qubit_gates=(Swap, ), + other_gates=(Barrier, )) + return setup + if device == 'Aspen-8': + setup = restrictedgateset.get_engine_list( + one_qubit_gates=(R, H, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z), + two_qubit_gates=(Swap, ), + other_gates=(Barrier, )) + return setup + if device == 'IonQ Device': + setup = restrictedgateset.get_engine_list( + one_qubit_gates=(H, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z, SqrtX), + two_qubit_gates=(Swap, ), + other_gates=(Barrier, )) + return setup + + +class DeviceOfflineError(Exception): + pass diff --git a/projectq/setups/awsbraket_test.py b/projectq/setups/awsbraket_test.py new file mode 100644 index 000000000..ef9f50447 --- /dev/null +++ b/projectq/setups/awsbraket_test.py @@ -0,0 +1,133 @@ +# Copyright 2021 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.setup.awsbraket.""" + +import pytest +from unittest.mock import MagicMock, Mock, patch +from botocore.response import StreamingBody +import botocore +from io import StringIO +import json + +import projectq.setups.awsbraket + + +search_value = { + "devices": [ + { + "deviceArn": "arn1", + "deviceName": "SV1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn2", + "deviceName": "Aspen-8", + "deviceType": "QPU", + "deviceStatus": "OFFLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn3", + "deviceName": "IonQ Device", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "pname2", + }, + ] + } + + +device_value_devicecapabilities = json.dumps( + { + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + "deviceLocation": "us-east-1", + }, + "action": { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], + } + }, + "paradigm": { + "qubitCount": 30, + "nativeGateSet": ["ccnot", "cy"], + "connectivity": {"fullyConnected": False, + "connectivityGraph": {"1": ["2", "3"]}}, + }, + "deviceParameters": { + "properties": {"braketSchemaHeader": {"const": + {"name": "braket.device_schema.rigetti.rigetti_device_parameters", + "version": "1"} + }}, + "definitions": {"GateModelParameters": {"properties": + {"braketSchemaHeader": {"const": + {"name": "braket.device_schema.gate_model_parameters", + "version": "1"} + }}}}, + }, + } +) + +device_value = { + "deviceName": "Aspen-8", + "deviceType": "QPU", + "providerName": "provider1", + "deviceStatus": "OFFLINE", + "deviceCapabilities": device_value_devicecapabilities, +} + +creds = { + 'AWS_ACCESS_KEY_ID': 'aws_access_key_id', + 'AWS_SECRET_KEY': 'aws_secret_key', + } + + +@patch('boto3.client') +@pytest.mark.parametrize("var_device", ['SV1', 'Aspen-8', 'IonQ Device']) +def test_awsbraket_get_engine_list(mock_boto3_client, var_device): + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.search_devices.return_value = search_value + mock_boto3_client.get_device.return_value = device_value + + engine_list = projectq.setups.awsbraket.get_engine_list(credentials=creds, + device=var_device) + assert len(engine_list) == 12 + + +@patch('boto3.client') +def test_awsbraket_error(mock_boto3_client): + + mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client.search_devices.return_value = search_value + mock_boto3_client.get_device.return_value = device_value + + with pytest.raises(projectq.setups.awsbraket.DeviceOfflineError): + projectq.setups.awsbraket.get_engine_list(credentials=creds, + device='Imaginary') diff --git a/setup.py b/setup.py index 2fb9547d7..ebd0b7ee3 100755 --- a/setup.py +++ b/setup.py @@ -430,6 +430,7 @@ def run_setup(with_cext): license='Apache 2', packages=find_packages(), distclass=Distribution, + extras_require={'braket': ['boto3', ]}, **kwargs) From 23dac9e92c4c98569cef27b333d7002034c5edde Mon Sep 17 00:00:00 2001 From: Nguyen Damien Date: Mon, 14 Jun 2021 11:23:19 +0200 Subject: [PATCH 06/11] GitHub actions, pre-commit hooks, pyproject.toml and more (#395) * Adding support for GitHub actions * Add workflow to upload to PyPi on GitHub releases submission * Add dependabot configuration file * Clang-Tidy and generation of requirement file for setup.py * Add support for pre-commit hooks and formatting workflow * Remove .pylintrc, .coveragerc and pytest.ini in favour of setup.cfg * Fix Clang-Tidy warnings * Fix flake8 warnings * Modify MANIFEST.in * Fix some issues with the documentation generation * Fix awsbraket tests to avoid running if boto3 is not installed * Remove Python2 compatibility code * Apply changes from pre-commit-hooks * Run Black on whole project * Run remove-tabs on whole project * Fix flake8 issues * Fix some warnings occurring while running the test suite * Add pyproject.toml * Adapt GitHub Action to use pyproject.toml * Add .editorconfig * Use setuptools-scm * Add GitHub Actions for creating new releases * Add CHANGELOG.md * Update pre-commit hooks * Update license headers of files with non-trivial changes * Add setup.cfg and pyproject.toml to MANIFEST.in * Change output name of version file * Move parameters for check-manifest into pyproject.toml * Address most comments (mostly merging strings) * Effects of running black and flake8 with new settings * Remove __div__ and __idiv__ usage in tests for QubitOperator * Fix typo * Fix build issues on Clang docker images * Make installation verbose on CI * Fix bug with classical simulator and negative bit shifts * Update flaky for GCC CI builds * Remove unneeded qubit allocation in meta.Loop tests --- .coveragerc | 4 - .editorconfig | 12 + .github/dependabot.yml | 10 + .github/workflows/ci.yml | 347 ++++++++++++ .github/workflows/draft_release.yml | 67 +++ .github/workflows/format.yml | 48 ++ .github/workflows/publish_release.yml | 143 +++++ .github/workflows/pull_request.yml | 18 + .gitignore | 4 +- .pre-commit-config.yaml | 64 +++ CHANGELOG.md | 66 +++ MANIFEST.in | 6 +- README.rst | 16 +- docs/README.rst | 2 +- docs/conf.py | 118 ++-- docs/examples.rst | 166 +++--- docs/index.rst | 22 +- docs/make.bat | 334 +++++------ docs/package_description.py | 70 +-- docs/projectq.rst | 4 +- docs/requirements.txt | 2 + docs/tutorials.rst | 234 ++++---- examples/aqt.py | 17 +- examples/bellpair_circuit.py | 3 +- examples/gate_zoo.py | 63 ++- examples/grover.py | 3 +- examples/hws4.py | 3 + examples/hws6.py | 9 +- examples/ibm.py | 13 +- examples/quantum_random_numbers.py | 1 + examples/quantum_random_numbers_ibm.py | 4 +- examples/shor.py | 69 +-- examples/teleport.py | 5 +- examples/teleport_circuit.py | 1 + projectq/__init__.py | 3 +- projectq/_version.py | 16 - projectq/backends/__init__.py | 2 +- projectq/backends/_aqt/__init__.py | 1 + projectq/backends/_aqt/_aqt.py | 81 +-- projectq/backends/_aqt/_aqt_http_client.py | 105 ++-- .../backends/_aqt/_aqt_http_client_test.py | 297 ++++------ projectq/backends/_aqt/_aqt_test.py | 86 ++- projectq/backends/_awsbraket/__init__.py | 12 +- projectq/backends/_awsbraket/_awsbraket.py | 171 ++++-- .../_awsbraket/_awsbraket_boto3_client.py | 191 +++---- .../_awsbraket_boto3_client_test.py | 203 +++---- .../_awsbraket_boto3_client_test_fixtures.py | 271 ++++----- .../backends/_awsbraket/_awsbraket_test.py | 389 +++++++------ .../_awsbraket/_awsbraket_test_fixtures.py | 155 ++--- projectq/backends/_circuits/__init__.py | 2 +- projectq/backends/_circuits/_drawer.py | 41 +- .../backends/_circuits/_drawer_matplotlib.py | 35 +- .../_circuits/_drawer_matplotlib_test.py | 22 +- projectq/backends/_circuits/_drawer_test.py | 9 +- projectq/backends/_circuits/_plot.py | 340 +++++------ projectq/backends/_circuits/_plot_test.py | 95 ++-- projectq/backends/_circuits/_to_latex.py | 489 ++++++++-------- projectq/backends/_circuits/_to_latex_test.py | 57 +- projectq/backends/_ibm/__init__.py | 1 + projectq/backends/_ibm/_ibm.py | 148 ++--- projectq/backends/_ibm/_ibm_http_client.py | 179 +++--- .../backends/_ibm/_ibm_http_client_test.py | 530 +++++++++--------- projectq/backends/_ibm/_ibm_test.py | 280 +++++---- projectq/backends/_printer.py | 16 +- projectq/backends/_printer_test.py | 19 +- projectq/backends/_resource.py | 21 +- projectq/backends/_resource_test.py | 11 +- projectq/backends/_sim/__init__.py | 1 + .../backends/_sim/_classical_simulator.py | 57 +- .../_sim/_classical_simulator_test.py | 40 +- .../_cppkernels/intrin/alignedallocator.hpp | 1 - .../_sim/_cppkernels/intrin/kernel1.hpp | 1 - .../_sim/_cppkernels/intrin/kernel2.hpp | 1 - .../_sim/_cppkernels/intrin/kernel3.hpp | 1 - .../_sim/_cppkernels/intrin/kernel4.hpp | 1 - .../_sim/_cppkernels/intrin/kernel5.hpp | 1 - .../_sim/_cppkernels/intrin/kernels.hpp | 1 - .../_sim/_cppkernels/nointrin/kernel1.hpp | 1 - .../_sim/_cppkernels/nointrin/kernel2.hpp | 1 - .../_sim/_cppkernels/nointrin/kernel3.hpp | 1 - .../_sim/_cppkernels/nointrin/kernel4.hpp | 1 - .../_sim/_cppkernels/nointrin/kernel5.hpp | 1 - .../backends/_sim/_cppkernels/simulator.hpp | 6 +- projectq/backends/_sim/_cppsim.cpp | 8 +- projectq/backends/_sim/_pysim.py | 132 +++-- projectq/backends/_sim/_simulator.py | 142 +++-- projectq/backends/_sim/_simulator_test.py | 180 +++--- .../backends/_sim/_simulator_test_fixtures.py | 46 ++ projectq/cengines/__init__.py | 19 +- projectq/cengines/_basicmapper.py | 7 +- projectq/cengines/_basicmapper_test.py | 25 +- projectq/cengines/_basics.py | 60 +- projectq/cengines/_basics_test.py | 55 +- projectq/cengines/_cmdmodifier.py | 3 +- projectq/cengines/_cmdmodifier_test.py | 5 +- projectq/cengines/_ibm5qubitmapper.py | 49 +- projectq/cengines/_ibm5qubitmapper_test.py | 42 +- projectq/cengines/_linearmapper.py | 145 +++-- projectq/cengines/_linearmapper_test.py | 343 +++++++----- projectq/cengines/_main.py | 36 +- projectq/cengines/_main_test.py | 38 +- projectq/cengines/_manualmapper.py | 3 +- projectq/cengines/_manualmapper_test.py | 10 +- projectq/cengines/_optimize.py | 81 ++- projectq/cengines/_optimize_test.py | 23 +- projectq/cengines/_replacer/__init__.py | 5 +- .../cengines/_replacer/_decomposition_rule.py | 12 +- .../_replacer/_decomposition_rule_set.py | 14 +- .../_replacer/_decomposition_rule_test.py | 10 +- projectq/cengines/_replacer/_replacer.py | 28 +- projectq/cengines/_replacer/_replacer_test.py | 82 +-- projectq/cengines/_swapandcnotflipper.py | 28 +- projectq/cengines/_swapandcnotflipper_test.py | 8 +- projectq/cengines/_tagremover.py | 3 +- projectq/cengines/_tagremover_test.py | 2 +- projectq/cengines/_testengine.py | 15 +- projectq/cengines/_testengine_test.py | 16 +- projectq/cengines/_twodmapper.py | 195 ++++--- projectq/cengines/_twodmapper_test.py | 197 ++++--- projectq/libs/__init__.py | 1 + projectq/libs/hist/__init__.py | 2 +- projectq/libs/hist/_histogram.py | 13 +- projectq/libs/hist/_histogram_test.py | 23 +- projectq/libs/math/__init__.py | 23 +- projectq/libs/math/_constantmath.py | 10 +- projectq/libs/math/_constantmath_test.py | 34 +- projectq/libs/math/_default_rules.py | 51 +- projectq/libs/math/_gates.py | 48 +- projectq/libs/math/_gates_math_test.py | 166 +++--- projectq/libs/math/_gates_test.py | 32 +- projectq/libs/math/_quantummath.py | 47 +- projectq/libs/math/_quantummath_test.py | 133 ++--- projectq/libs/revkit/__init__.py | 1 + projectq/libs/revkit/_control_function.py | 15 +- .../libs/revkit/_control_function_test.py | 19 +- projectq/libs/revkit/_permutation.py | 12 +- projectq/libs/revkit/_permutation_test.py | 23 +- projectq/libs/revkit/_phase.py | 15 +- projectq/libs/revkit/_phase_test.py | 20 +- projectq/libs/revkit/_utils.py | 2 +- projectq/meta/__init__.py | 15 +- projectq/meta/_compute.py | 60 +- projectq/meta/_compute_test.py | 53 +- projectq/meta/_control.py | 7 +- projectq/meta/_control_test.py | 8 +- projectq/meta/_dagger.py | 23 +- projectq/meta/_dagger_test.py | 6 +- projectq/meta/_dirtyqubit.py | 3 +- projectq/meta/_dirtyqubit_test.py | 2 +- projectq/meta/_logicalqubit.py | 6 +- projectq/meta/_logicalqubit_test.py | 2 +- projectq/meta/_loop.py | 37 +- projectq/meta/_loop_test.py | 53 +- projectq/meta/_util.py | 1 + projectq/meta/_util_test.py | 2 +- projectq/ops/__init__.py | 42 +- projectq/ops/_basics.py | 50 +- projectq/ops/_basics_test.py | 79 +-- projectq/ops/_command.py | 68 ++- projectq/ops/_command_test.py | 39 +- projectq/ops/_gates.py | 208 ++++--- projectq/ops/_gates_test.py | 147 ++--- projectq/ops/_metagates.py | 33 +- projectq/ops/_metagates_test.py | 54 +- projectq/ops/_qaagate.py | 5 +- projectq/ops/_qaagate_test.py | 9 +- projectq/ops/_qftgate.py | 2 + projectq/ops/_qftgate_test.py | 2 +- projectq/ops/_qpegate.py | 2 + projectq/ops/_qpegate_test.py | 2 +- projectq/ops/_qubit_operator.py | 160 +++--- projectq/ops/_qubit_operator_test.py | 134 ++--- projectq/ops/_shortcuts.py | 4 +- projectq/ops/_shortcuts_test.py | 2 +- projectq/ops/_state_prep.py | 2 + projectq/ops/_state_prep_test.py | 4 +- projectq/ops/_time_evolution.py | 31 +- projectq/ops/_time_evolution_test.py | 22 +- .../ops/_uniformly_controlled_rotation.py | 21 +- .../_uniformly_controlled_rotation_test.py | 17 +- projectq/setups/__init__.py | 1 + projectq/setups/aqt.py | 19 +- projectq/setups/aqt_test.py | 54 +- projectq/setups/awsbraket.py | 46 +- projectq/setups/awsbraket_test.py | 164 +++--- projectq/setups/decompositions/__init__.py | 105 ++-- projectq/setups/decompositions/_gates_test.py | 68 ++- .../decompositions/amplitudeamplification.py | 6 +- .../amplitudeamplification_test.py | 59 +- .../decompositions/arb1qubit2rzandry.py | 114 ++-- .../decompositions/arb1qubit2rzandry_test.py | 91 +-- projectq/setups/decompositions/barrier.py | 10 +- .../setups/decompositions/barrier_test.py | 4 +- .../decompositions/carb1qubit2cnotrzandry.py | 81 +-- .../carb1qubit2cnotrzandry_test.py | 62 +- projectq/setups/decompositions/cnot2cz.py | 8 +- .../setups/decompositions/cnot2cz_test.py | 37 +- projectq/setups/decompositions/cnot2rxx.py | 9 +- .../setups/decompositions/cnot2rxx_test.py | 45 +- .../setups/decompositions/cnu2toffoliandcu.py | 15 +- .../decompositions/cnu2toffoliandcu_test.py | 49 +- projectq/setups/decompositions/crz2cxandrz.py | 10 +- projectq/setups/decompositions/entangle.py | 10 +- projectq/setups/decompositions/globalphase.py | 10 +- projectq/setups/decompositions/h2rx.py | 13 +- projectq/setups/decompositions/h2rx_test.py | 51 +- projectq/setups/decompositions/ph2r.py | 10 +- .../setups/decompositions/phaseestimation.py | 23 +- .../decompositions/phaseestimation_test.py | 96 ++-- .../decompositions/qft2crandhadamard.py | 6 +- .../setups/decompositions/qubitop2onequbit.py | 10 +- .../decompositions/qubitop2onequbit_test.py | 38 +- projectq/setups/decompositions/r2rzandph.py | 10 +- projectq/setups/decompositions/rx2rz.py | 10 +- projectq/setups/decompositions/rx2rz_test.py | 30 +- projectq/setups/decompositions/ry2rz.py | 14 +- projectq/setups/decompositions/ry2rz_test.py | 30 +- projectq/setups/decompositions/rz2rx.py | 13 +- projectq/setups/decompositions/rz2rx_test.py | 34 +- .../setups/decompositions/sqrtswap2cnot.py | 11 +- .../decompositions/sqrtswap2cnot_test.py | 33 +- .../setups/decompositions/stateprep2cnot.py | 51 +- .../decompositions/stateprep2cnot_test.py | 11 +- projectq/setups/decompositions/swap2cnot.py | 10 +- .../setups/decompositions/time_evolution.py | 34 +- .../decompositions/time_evolution_test.py | 93 +-- .../decompositions/toffoli2cnotandtgate.py | 11 +- .../uniformlycontrolledr2cnot.py | 98 ++-- .../uniformlycontrolledr2cnot_test.py | 114 ++-- projectq/setups/default.py | 24 +- projectq/setups/grid.py | 69 ++- projectq/setups/grid_test.py | 26 +- projectq/setups/ibm.py | 34 +- projectq/setups/ibm_test.py | 57 +- projectq/setups/linear.py | 69 ++- projectq/setups/linear_test.py | 26 +- projectq/setups/restrictedgateset.py | 54 +- projectq/setups/restrictedgateset_test.py | 33 +- projectq/setups/trapped_ion_decomposer.py | 8 +- .../setups/trapped_ion_decomposer_test.py | 45 +- projectq/tests/__init__.py | 1 + projectq/tests/_factoring_test.py | 49 +- projectq/types/__init__.py | 1 + projectq/types/_qubit.py | 24 +- projectq/types/_qubit_test.py | 5 +- pyproject.toml | 85 +++ pytest.ini | 9 - requirements.txt | 8 - requirements_tests.txt | 3 + setup.cfg | 58 ++ setup.py | 385 +++++++++---- 251 files changed, 7582 insertions(+), 5938 deletions(-) delete mode 100644 .coveragerc create mode 100644 .editorconfig create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/draft_release.yml create mode 100644 .github/workflows/format.yml create mode 100644 .github/workflows/publish_release.yml create mode 100644 .github/workflows/pull_request.yml create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGELOG.md create mode 100644 docs/requirements.txt delete mode 100755 projectq/_version.py create mode 100644 projectq/backends/_sim/_simulator_test_fixtures.py create mode 100644 pyproject.toml delete mode 100755 pytest.ini delete mode 100755 requirements.txt create mode 100644 requirements_tests.txt create mode 100644 setup.cfg diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 1650dd93d..000000000 --- a/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[run] - -omit = *_test.py - *_fixtures.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..5e98ae4a7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = false +indent_style = space +indent_size = 4 + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..7fddd081d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + ignore: + # Optional: Official actions have moving tags like v1; + # if you use those, you don't need updates. + - dependency-name: "actions/*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..7f95d55e5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,347 @@ +name: CI + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - develop + - v* + +jobs: + standard: + strategy: + fail-fast: false + matrix: + runs-on: [ubuntu-latest, windows-latest, macos-latest] + python: + - 3.6 + - 3.7 + - 3.8 + - 3.9 + + name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" + runs-on: ${{ matrix.runs-on }} + + steps: + - uses: actions/checkout@v2 + + - name: Get history and tags for SCM versioning to work + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + architecture: 'x64' + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(python -m pip cache dir)" + + - name: Cache wheels + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-${{ matrix.python }}-pip-${{ hashFiles('**/setup.cfg') }} + restore-keys: ${{ runner.os }}-${{ matrix.python }}-pip- + + - name: Prepare env + run: | + python setup.py gen_reqfile --include-extras + python -m pip install -r requirements.txt --prefer-binary + python -m pip install -r requirements_tests.txt --prefer-binary + python -m pip install coveralls + + - name: Setup annotations on Linux + if: runner.os == 'Linux' + run: python -m pip install pytest-github-actions-annotate-failures + + - name: Build and install package + run: python -m pip install -ve .[braket] + + - name: Pytest + run: | + echo 'backend: Agg' > matplotlibrc + python -m pytest -p no:warnings --cov=projectq + + - name: Coveralls.io + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_FLAG_NAME: python-${{ matrix.python }}-${{ matrix.runs-on }}-x64 + COVERALLS_PARALLEL: true + + + finish: + needs: standard + runs-on: ubuntu-latest + container: python:3-slim + steps: + - name: Coveralls Finished + run: | + pip3 install --upgrade coveralls + coveralls --finish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + + clang: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + clang: + - 3.5 # version for full C++14 support (3.4 fails because of -fstack-protector-strong) + - 5 # earliest version for reasonable C++17 support + - 10 # version for full C++17 support (with patches) + - latest + env: + CC: clang + CXX: clang++ + PROJECTQ_CLEANUP_COMPILER_FLAGS: ${{ (matrix.clang < 10) && 1 || 0 }} + + name: "🐍 3 • Clang ${{ matrix.clang }} • x64" + container: "silkeh/clang:${{ matrix.clang }}" + + steps: + - uses: actions/checkout@v2 + + - name: Get history and tags for SCM versioning to work + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + + - name: Prepare env + run: > + apt-get update && apt-get install -y python3-dev python3-pip python3-setuptools python3-wheel + python3-numpy python3-scipy python3-matplotlib python3-requests python3-networkx + python3-pytest python3-pytest-cov python3-flaky + --no-install-recommends + + - name: Prepare Python env + run: | + python3 setup.py gen_reqfile --include-extras + python3 -m pip install -r requirements.txt --prefer-binary + python3 -m pip install -r requirements_tests.txt --prefer-binary + + - name: Upgrade pybind11 and flaky + run: python3 -m pip install --upgrade pybind11 flaky --prefer-binary + + - name: Build and install package + run: python3 -m pip install -ve .[braket] + + - name: Pytest + run: | + echo 'backend: Agg' > matplotlibrc + python3 -m pytest -p no:warnings + + + gcc: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + gcc: + - 7 # C++17 earliest version + - latest + + name: "🐍 3 • GCC ${{ matrix.gcc }} • x64" + container: "gcc:${{ matrix.gcc }}" + + steps: + - uses: actions/checkout@v2 + + - name: Get history and tags for SCM versioning to work + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + + - name: Prepare env + run: > + apt-get update && apt-get install -y python3-dev python3-pip python3-setuptools python3-wheel + python3-numpy python3-scipy python3-matplotlib python3-requests python3-networkx + python3-pytest python3-pytest-cov python3-flaky + --no-install-recommends + + - name: Prepare Python env + run: | + python3 setup.py gen_reqfile --include-extras + python3 -m pip install -r requirements.txt --prefer-binary + python3 -m pip install -r requirements_tests.txt --prefer-binary + + - name: Upgrade pybind11 and flaky + run: python3 -m pip install --upgrade pybind11 flaky --prefer-binary + + - name: Build and install package + run: python3 -m pip install -ve .[braket] + + - name: Pytest + run: | + echo 'backend: Agg' > matplotlibrc + python3 -m pytest -p no:warnings + + + # Testing on CentOS (manylinux uses a centos base, and this is an easy way + # to get GCC 4.8, which is the manylinux1 compiler). + centos: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + centos: + - 7 # GCC 4.8 + - 8 + + name: "🐍 3 • CentOS ${{ matrix.centos }} • x64" + container: "centos:${{ matrix.centos }}" + + steps: + - name: Enable cache for yum + run: echo 'keepcache=1' >> /etc/yum.conf + + - name: Setup yum cache + uses: actions/cache@v2 + with: + path: | + /var/cache/yum/ + /var/cache/dnf/ + key: ${{ runner.os }}-centos${{ matrix.centos }}-yum-${{ secrets.yum_cache }} + + - name: Add Python 3 and other dependencies + run: yum update -y && yum install -y python3-devel gcc-c++ make + + - name: Setup Endpoint repository (CentOS 7 only) + if: matrix.centos == 7 + run: yum -y install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpm + + - name: Install Git > 2.18 + run: yum install -y git + + - uses: actions/checkout@v2 + + - name: Get history and tags for SCM versioning to work + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + + - name: Create pip cache dir + run: mkdir -p ~/.cache/pip + + - name: Cache wheels + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-centos${{ matrix.centos }}-pip-${{ hashFiles('**/setup.cfg') }} + restore-keys: ${{ runner.os }}-centos-pip- + + - name: Update pip + run: python3 -m pip install --upgrade pip + + - name: Install dependencies + run: | + python3 setup.py gen_reqfile --include-extras + python3 -m pip install -r requirements.txt --prefer-binary + python3 -m pip install -r requirements_tests.txt --prefer-binary + + - name: Build and install package + run: python3 -m pip install -ve .[braket] + + - name: Pytest + run: | + echo 'backend: Agg' > matplotlibrc + python3 -m pytest -p no:warnings + + + documentation: + name: "Documentation build test" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Create pip cache dir + run: mkdir -p ~/.cache/pip + + - name: Cache wheels + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-doc-pip-${{ hashFiles('**/setup.cfg') }} + restore-keys: ${{ runner.os }}-doc-pip- + + - name: Get history and tags for SCM versioning to work + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + + - uses: actions/setup-python@v2 + + - name: Install docs & setup requirements + run: | + python3 setup.py gen_reqfile --include-extras + python3 -m pip install -r requirements.txt --prefer-binary + python3 -m pip install -r docs/requirements.txt --prefer-binary + python3 -m pip install . + + - name: Build docs + run: python3 -m sphinx -b html docs docs/.build + + - name: Make SDist + run: python3 setup.py sdist + + + win32-msvc2017: + name: "🐍 ${{ matrix.python }} • MSVC 2017 • x64" + runs-on: windows-2016 + strategy: + fail-fast: false + matrix: + python: + - 3.6 + - 3.7 + - 3.8 + - 3.9 + + steps: + - uses: actions/checkout@v2 + + - name: Get history and tags for SCM versioning to work + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(python -m pip cache dir)" + + - name: Cache wheels + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-${{ matrix.python }}-pip-${{ hashFiles('**/setup.cfg') }} + restore-keys: ${{ runner.os }}-${{ matrix.python }}-pip- + + - name: Setup 🐍 ${{ matrix.python }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + + - name: Prepare env + run: | + python setup.py gen_reqfile --include-extras + python -m pip install -r requirements.txt --prefer-binary + python -m pip install -r requirements_tests.txt --prefer-binary + + - name: Build and install package + run: python -m pip install -ve .[braket] + + - name: Run all checks + run: | + echo 'backend: Agg' > matplotlibrc + python3 -m pytest -p no:warnings diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml new file mode 100644 index 000000000..3843cc5be --- /dev/null +++ b/.github/workflows/draft_release.yml @@ -0,0 +1,67 @@ +name: "Draft new release" + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to release' + required: true + +jobs: + new-release: + name: "Draft a new release" + runs-on: ubuntu-latest + steps: + - name: Install git-flow + run: sudo apt update && sudo apt install -y git-flow + + - uses: actions/checkout@v2 + + - name: Configure git-flow + run: | + git fetch --tags --depth=1 origin master develop + git flow init --default --tag v + + - name: Create release branch + run: git flow release start ${{ github.event.inputs.version }} + + - name: Update changelog + uses: thomaseizinger/keep-a-changelog-new-release@1.2.1 + with: + version: ${{ github.event.inputs.version }} + + - name: Initialize mandatory git config + run: | + git config user.name "GitHub actions" + git config user.email noreply@github.com + + - name: Commit changelog and manifest files + id: make-commit + run: | + git add CHANGELOG.md + git commit --message "Preparing release v${{ github.event.inputs.version }}" + + echo "::set-output name=commit::$(git rev-parse HEAD)" + + - name: Push new branch + run: git flow release publish ${{ github.event.inputs.version }} + + - name: Create pull request + uses: thomaseizinger/create-pull-request@1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + head: release/${{ github.event.inputs.version }} + base: master + title: Release version ${{ github.event.inputs.version }} + reviewers: ${{ github.actor }} + # Write a nice message to the user. + # We are claiming things here based on the `publish-new-release.yml` workflow. + # You should obviously adopt it to say the truth depending on your release workflow :) + body: | + Hi @${{ github.actor }}! + + This PR was created in response to a manual trigger of the release workflow here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}. + I've updated the changelog and bumped the versions in the manifest files in this commit: ${{ steps.make-commit.outputs.commit }}. + + Merging this PR will create a GitHub release and upload any assets that are created as part of the release build. diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 000000000..0cd0b3e16 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,48 @@ +name: Format + +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - stable + - "v*" + +jobs: + pre-commit: + name: Format and static analysis + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Get history and tags for SCM versioning to work + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - uses: actions/setup-python@v2 + - uses: pre-commit/action@v2.0.3 + with: + # Slow hooks are marked with manual - slow is okay here, run them too + extra_args: --hook-stage manual --all-files + + clang-tidy: + name: Clang-Tidy + runs-on: ubuntu-latest + container: silkeh/clang:10 + env: + CC: clang + CXX: clang++ + + steps: + - uses: actions/checkout@v2 + + - name: Prepare env + run: > + apt-get update && apt-get install -y python3-dev python3-pip python3-setuptools python3-wheel + --no-install-recommends + + - name: Upgrade pybind11 + run: python3 -m pip install --upgrade pybind11 --prefer-binary + + - name: Run Clang-Tidy + run: python3 setup.py clang_tidy --warning-as-errors diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml new file mode 100644 index 000000000..3bb2ab43e --- /dev/null +++ b/.github/workflows/publish_release.yml @@ -0,0 +1,143 @@ +name: "Publish new release" + +on: + push: + tags: + - v[0-9]+.* + pull_request: + branches: + - master + types: + - closed + +jobs: + packaging: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) + strategy: + matrix: + os: [ubuntu-20.04, windows-2019, macos-10.15] + + steps: + - uses: actions/checkout@v2 + + - name: Get history and tags for SCM versioning to work + run: | + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + + - name: Build wheels + uses: joerick/cibuildwheel@v1.11.0 + env: + CIBW_ARCHS: auto64 + CIBW_SKIP: cp27-* pp* cp35-* + CIBW_BEFORE_BUILD: python -m pip install pybind11 + + - name: Build source distribution + if: runner.os == 'Linux' + run: python3 setup.py sdist -d wheelhouse + + - name: Check metadata + run: | + python3 -m pip install twine --prefer-binary + python3 -m twine check wheelhouse/* + + - uses: actions/upload-artifact@v2 + with: + name: packages + path: ./wheelhouse/* + + release: + name: Publish new release + runs-on: ubuntu-latest + needs: packaging + steps: + - name: Extract version from tag name + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + run: | + TAG_NAME="${GITHUB_REF/refs\/tags\//}" + VERSION=${TAG_NAME#v} + + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + + - name: Extract version from branch name (for release branches) + if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/') + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + VERSION=${BRANCH_NAME#release/} + + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + + - name: Extract version from branch name (for hotfix branches) + if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + VERSION=${BRANCH_NAME#hotfix/} + + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + + - uses: actions/checkout@v2 + + # Downloads all to directories matching the artifact names + - uses: actions/download-artifact@v2 + + # Code below inspired from this action: + # - uses: taiki-e/create-gh-release-action@v1 + # with: + # title: ProjectQ $tag + # changelog: CHANGELOG.md + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create release + env: + target: x86_64-unknown-linux-musl + parse_changelog_tag: v0.3.0 + changelog: CHANGELOG.md + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # https://github.com/taiki-e/parse-changelog + curl -LsSf "https://github.com/taiki-e/parse-changelog/releases/download/${parse_changelog_tag}/parse-changelog-${target}.tar.gz" | tar xzf - + notes=$(./parse-changelog "${changelog}" "${RELEASE_VERSION}") + rm -f ./parse-changelog + + if [[ "${tag}" =~ ^v?[0-9\.]+-[a-zA-Z_0-9\.-]+(\+[a-zA-Z_0-9\.-]+)?$ ]]; then + prerelease="--prerelease" + fi + gh release create "v${RELEASE_VERSION}" ${prerelease:-} --title "ProjectQ v${RELEASE_VERSION}" --notes "${notes:-}" packages/* + + + upload_to_pypi: + name: Upload to PyPI + runs-on: ubuntu-latest + needs: release # Only upload to PyPi if everything was successful + steps: + - uses: actions/setup-python@v2 + + # Downloads all to directories matching the artifact names + - uses: actions/download-artifact@v2 + + - name: Publish standard package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.pypi_password }} + packages_dir: packages/ + + master_to_develop_pr: + name: Merge master back into develop + runs-on: ubuntu-latest + needs: release # Only create PR if everything was successful + steps: + - name: Merge master into develop branch + uses: thomaseizinger/create-pull-request@1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + head: master + base: develop + title: Merge master into develop branch + body: | + This PR merges the master branch back into develop. + This happens to ensure that the updates that happend on the release branch, i.e. CHANGELOG and manifest updates are also present on the develop branch. diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 000000000..cfcb7d0a1 --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,18 @@ +name: PR +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] + +jobs: + # Enforces the update of a changelog file on every pull request + changelog: + runs-on: ubuntu-latest + if: github.ref != 'refs/heads/master' + steps: + - uses: actions/checkout@v2 + + - id: changelog-enforcer + uses: dangoslen/changelog-enforcer@v2 + with: + changeLogPath: 'CHANGELOG.md' + skipLabels: 'Skip-Changelog' diff --git a/.gitignore b/.gitignore index 24d243a5b..3b349b6a1 100644 --- a/.gitignore +++ b/.gitignore @@ -173,8 +173,10 @@ dmypy.json # ============================================================================== +VERSION.txt + # Windows artifacts thumbs.db # Mac OSX artifacts -*.DS_Store \ No newline at end of file +*.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..523c4e98e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,64 @@ +# To use: +# +# pre-commit run -a +# +# Or: +# +# pre-commit install # (runs every time you commit in git) +# +# To update this file: +# +# pre-commit autoupdate +# +# See https://github.com/pre-commit/pre-commit + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: check-toml + - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + - id: requirements-txt-fixer + - id: trailing-whitespace + - id: fix-encoding-pragma + +# Changes tabs to spaces +- repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.1.10 + hooks: + - id: remove-tabs + +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + exclude: ^(docs/.*|tools/.*)$ + +- repo: https://github.com/psf/black + rev: 21.5b1 + hooks: + - id: black + language_version: python3 + # This is a slow hook, so only run this if --hook-stage manual is passed + stages: [manual] + +- repo: https://github.com/pre-commit/mirrors-pylint + rev: 'v3.0.0a3' + hooks: + - id: pylint + args: ['--score=n', '--exit-zero'] + # This is a slow hook, so only run this if --hook-stage manual is passed + stages: [manual] + +- repo: https://github.com/mgedmin/check-manifest + rev: "0.46" + hooks: + - id: check-manifest + additional_dependencies: ['setuptools-scm', 'pybind11>=2.6'] diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..5e6d331f2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,66 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Support for GitHub Actions + * Build and testing on various plaforms and compilers + * Automatic draft of new release + * Automatic publication of new release once ready + * Automatic upload of releases artifacts to PyPi and GitHub +- Use ``setuptools-scm`` for versioning +- Added ``.editorconfig` file +- Added ``pyproject.toml`` and ``setup.cfg`` +- Added CHANGELOG.md + +### Deprecated + +- Compatibility with Python <= 3.5 + +### Removed + +- Compatibility with Python 2.7 + +## [0.5.1] - 2019-02-15 + +### Added + +- Add histogram plot function for measurement results (thanks @AriJordan ) +- New backend for AQT (thanks @dbretaud ) + +### Fixed + +- Fix Qiskit backend (thanks @dbretaud ) +- Fix bug with matplotlib drawer (thanks @AriJordan ) + +## [0.5.0] - 2020 + +### Added + +- New [PhaseEstimation](https://projectq.readthedocs.io/en/latest/projectq.ops.html#projectq.ops.QPE) and [AmplitudeAmplification](https://projectq.readthedocs.io/en/latest/projectq.ops.html#projectq.ops.QAA) gates (thanks @fernandodelaiglesia) +- New [Rxx](https://projectq.readthedocs.io/en/latest/projectq.ops.html#projectq.ops.Rxx), [Ryy](https://projectq.readthedocs.io/en/latest/projectq.ops.html#projectq.ops.Ryy) and [Rzz](https://projectq.readthedocs.io/en/latest/projectq.ops.html#projectq.ops.Rzz) gates (thanks @dwierichs) +- New decomposition rules and compiler setup for trapped ion quantum based computers (thanks @dbretaud) +- Added basic circuit drawer compiler engine based on Matplotlib [CircuitDrawerMatplotlib](https://projectq.readthedocs.io/en/latest/projectq.backends.html#projectq.backends.CircuitDrawerMatplotlib) (thanks @Bombenchris) + +### Changed + +- Significantly improved C++ simulator performances (thanks @melven) +- Allow user modification of the qubit drawing order for the `CircuitDrawer` compiler engine (thanks @alexandrupaler) +- Update to the installation script. The installation now automatically defaults to the pure Python implementation if compilation of the C++ simulator (or other C++ modules) should fail (#337) +- Automatic generation of the documentation (#339) + +### Fixes + +- Fixed connection issues between IBM backend and the IBM Quantum Experience API (thanks @alexandrupaler) + +### Deprecated + +The ProjectQ v0.5.x release branch is the last one that is guaranteed to work with Python 2.7.x. + +Future releases might introduce changes that will require Python 3.5 (Python 3.4 and earlier have already been declared deprecated at the time of this writing) diff --git a/MANIFEST.in b/MANIFEST.in index b087d38fd..aa5655eab 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,9 +1,11 @@ include LICENSE +include CHANGELOG.md include MANIFEST.in include NOTICE -include pytest.ini include README.rst -include requirements.txt +include requirements*.txt include setup.py +include setup.cfg +include pyproject.toml recursive-include projectq *.py *.hpp *.cpp diff --git a/README.rst b/README.rst index 4a14403bc..5232cd889 100755 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ ProjectQ - An open source software framework for quantum computing .. image:: https://badge.fury.io/py/projectq.svg :target: https://badge.fury.io/py/projectq - + .. image:: https://img.shields.io/badge/python-2.7%2C%203.4%2C%203.5%2C%203.6-brightgreen.svg @@ -108,7 +108,7 @@ To run a program on the IBM Quantum Experience chips, all one has to do is choos import projectq.setups.ibm from projectq.backends import IBMBackend - + token='MY_TOKEN' device='ibmq_16_melbourne' compiler_engines = projectq.setups.ibm.get_engine_list(token=token,device=device) @@ -125,7 +125,7 @@ To run a program on the AQT trapped ion quantum computer, choose the `AQTBackend import projectq.setups.aqt from projectq.backends import AQTBackend - + token='MY_TOKEN' device='aqt_device' compiler_engines = projectq.setups.aqt.get_engine_list(token=token,device=device) @@ -180,7 +180,7 @@ ProjectQ has a high-performance simulator which allows simulating up to about 30 The advanced features of the simulator are also particularly useful to investigate algorithms for the simulation of quantum systems. For example, the simulator can evolve a quantum system in time (without Trotter errors) and it gives direct access to expectation values of Hamiltonians leading to extremely fast simulations of VQE type algorithms: .. code-block:: python - + from projectq import MainEngine from projectq.ops import All, Measure, QubitOperator, TimeEvolution @@ -220,19 +220,19 @@ Please cite When using ProjectQ for research projects, please cite -- Damian S. Steiger, Thomas Häner, and Matthias Troyer "ProjectQ: An +- Damian S. Steiger, Thomas Haener, and Matthias Troyer "ProjectQ: An Open Source Software Framework for Quantum Computing" `Quantum 2, 49 (2018) `__ (published on `arXiv `__ on 23 Dec 2016) -- Thomas Häner, Damian S. Steiger, Krysta M. Svore, and Matthias Troyer - "A Software Methodology for Compiling Quantum Programs" `Quantum Sci. Technol. 3 (2018) 020501 `__ +- Thomas Haener, Damian S. Steiger, Krysta M. Svore, and Matthias Troyer + "A Software Methodology for Compiling Quantum Programs" `Quantum Sci. Technol. 3 (2018) 020501 `__ (published on `arXiv `__ on 5 Apr 2016) Authors ------- The first release of ProjectQ (v0.1) was developed by `Thomas -Häner `__ +Haener `__ and `Damian S. Steiger `__ in the group of `Prof. Dr. Matthias diff --git a/docs/README.rst b/docs/README.rst index 8edb90a73..96dacc34e 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -1,4 +1,4 @@ -Documentation +Documentation ============= .. image:: https://readthedocs.org/projects/projectq/badge/?version=latest diff --git a/docs/conf.py b/docs/conf.py index 4083653fa..5097d7854 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- #!/usr/bin/env python3 # -*- coding: utf-8 -*- @@ -20,9 +21,13 @@ # import os import sys + sys.path.insert(0, os.path.abspath('..')) +from importlib.metadata import version + import projectq + # Also import all the modules that are not automatically imported import projectq.libs.math import projectq.libs.revkit @@ -81,12 +86,8 @@ # # The short X.Y version. -# This reads the __version__ variable from projectq/_version.py -exec(open('../projectq/_version.py').read()) - -version = __version__ -# The full version, including alpha/beta/rc tags. -release = __version__ +release = version('projectq') +version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -267,15 +268,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -336,8 +334,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'projectq', 'projectq Documentation', author, 'projectq', - 'One line description of project.', 'Miscellaneous'), + ( + master_doc, + 'projectq', + 'projectq Documentation', + author, + 'projectq', + 'One line description of project.', + 'Miscellaneous', + ), ] # Documents to append as an appendix to all manuals. @@ -382,13 +387,12 @@ def linkcode_resolve(domain, info): else: github_tag = rtd_tag else: - github_tag = 'v' + __version__ + github_tag = 'v' + version if domain != 'py': return None else: try: - if ('module' in info and 'fullname' in info - and info['module'] and info['fullname']): + if 'module' in info and 'fullname' in info and info['module'] and info['fullname']: obj = eval(info['module'] + '.' + info['fullname']) else: return None @@ -410,8 +414,7 @@ def linkcode_resolve(domain, info): if len(new_higher_name) <= 1: obj = eval(info['module']) else: - obj = eval(info['module'] + '.' - + '.'.join(new_higher_name[:-1])) + obj = eval(info['module'] + '.' + '.'.join(new_higher_name[:-1])) filepath = inspect.getsourcefile(obj) line_number = inspect.getsourcelines(obj)[1] except: @@ -419,14 +422,14 @@ def linkcode_resolve(domain, info): # Only require relative path projectq/relative_path projectq_path = inspect.getsourcefile(projectq)[:-11] relative_path = os.path.relpath(filepath, projectq_path) - url = (github_url + github_tag + "/projectq/" + relative_path + "#L" - + str(line_number)) + url = github_url + github_tag + "/projectq/" + relative_path + "#L" + str(line_number) return url # ------------------------------------------------------------------------------ import importlib + sys.path.append(os.path.abspath('.')) desc = importlib.import_module('package_description') @@ -447,16 +450,21 @@ def linkcode_resolve(domain, info): descriptions = [ PackageDescription('backends'), - PackageDescription('cengines', - desc=''' + PackageDescription( + 'cengines', + desc=''' The ProjectQ compiler engines package. -'''), - PackageDescription('libs.math', - desc=''' +''', + ), + PackageDescription( + 'libs.math', + desc=''' A tiny math library which will be extended thoughout the next weeks. Right now, it only contains the math functions necessary to run Beauregard's implementation of Shor's algorithm. -'''), - PackageDescription('libs.revkit', - desc=''' +''', + ), + PackageDescription( + 'libs.revkit', + desc=''' This library integrates `RevKit `_ into ProjectQ to allow some automatic synthesis routines for reversible logic. The library adds the following operations that can be used to construct quantum @@ -481,27 +489,37 @@ def linkcode_resolve(domain, info): * Mathias Soeken, Thomas Haener, and Martin Roetteler "Programming Quantum Computers Using Design Automation," in: Design Automation and Test in Europe (2018) [`arXiv:1803.01022 `_] ''', - module_special_members='__init__,__or__'), - PackageDescription('libs', - desc=''' + module_special_members='__init__,__or__', + ), + PackageDescription( + 'libs', + desc=''' The library collection of ProjectQ which, for now, consists of a tiny math library and an interface library to RevKit. Soon, more libraries will be added. -'''), - PackageDescription('meta', - desc=''' +''', + ), + PackageDescription( + 'meta', + desc=''' Contains meta statements which allow more optimal code while making it easier for users to write their code. Examples are `with Compute`, followed by an automatic uncompute or `with Control`, which allows the user to condition an entire code block upon the state of a qubit. -'''), - PackageDescription('ops', - desc=''' +''', + ), + PackageDescription( + 'ops', + desc=''' The operations collection consists of various default gates and is a work-in-progress, as users start to work with ProjectQ. ''', - module_special_members='__init__,__or__'), - PackageDescription('setups.decompositions', - desc=''' + module_special_members='__init__,__or__', + ), + PackageDescription( + 'setups.decompositions', + desc=''' The decomposition package is a collection of gate decomposition / replacement rules which can be used by, e.g., the AutoReplacer engine. -'''), - PackageDescription('setups', - desc=''' +''', + ), + PackageDescription( + 'setups', + desc=''' The setups package contains a collection of setups which can be loaded by the `MainEngine`. Each setup contains a `get_engine_list` function which returns a list of compiler engines: Example: @@ -515,21 +533,25 @@ def linkcode_resolve(domain, info): The subpackage decompositions contains all the individual decomposition rules which can be given to, e.g., an `AutoReplacer`. ''', - submodules_desc=''' + submodules_desc=''' Each of the submodules contains a setup which can be used to specify the `engine_list` used by the `MainEngine` :''', - submodule_special_members='__init__'), + submodule_special_members='__init__', + ), PackageDescription( - 'types', ''' + 'types', + ''' The types package contains quantum types such as Qubit, Qureg, and WeakQubitRef. With further development of the math library, also quantum integers, quantum fixed point numbers etc. will be added. -'''), +''', + ), ] # ------------------------------------------------------------------------------ # Automatically generate ReST files for each package of ProjectQ -docgen_path = os.path.join(os.path.dirname(os.path.abspath('__file__')), - '_doc_gen') -os.mkdir(docgen_path) +docgen_path = os.path.join(os.path.dirname(os.path.abspath('__file__')), '_doc_gen') +if not os.path.isdir(docgen_path): + os.mkdir(docgen_path) + for desc in descriptions: fname = os.path.join(docgen_path, 'projectq.{}.rst'.format(desc.name)) lines = None diff --git a/docs/examples.rst b/docs/examples.rst index c337344eb..609b8bf90 100755 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -6,7 +6,7 @@ Examples All of these example codes **and more** can be found on `GitHub `_. .. toctree:: - :maxdepth: 2 + :maxdepth: 2 Quantum Random Numbers ---------------------- @@ -14,36 +14,36 @@ Quantum Random Numbers The most basic example is a quantum random number generator (QRNG). It can be found in the examples-folder of ProjectQ. The code looks as follows .. literalinclude:: ../examples/quantum_random_numbers.py - :tab-width: 2 + :tab-width: 2 Running this code three times may yield, e.g., .. code-block:: bash - $ python examples/quantum_random_numbers.py - Measured: 0 - $ python examples/quantum_random_numbers.py - Measured: 0 - $ python examples/quantum_random_numbers.py - Measured: 1 + $ python examples/quantum_random_numbers.py + Measured: 0 + $ python examples/quantum_random_numbers.py + Measured: 0 + $ python examples/quantum_random_numbers.py + Measured: 1 These values are obtained by simulating this quantum algorithm classically. By changing three lines of code, we can run an actual quantum random number generator using the IBM Quantum Experience back-end: .. code-block:: bash - $ python examples/quantum_random_numbers_ibm.py - Measured: 1 - $ python examples/quantum_random_numbers_ibm.py - Measured: 0 + $ python examples/quantum_random_numbers_ibm.py + Measured: 1 + $ python examples/quantum_random_numbers_ibm.py + Measured: 0 All you need to do is: - * Create an account for `IBM's Quantum Experience `_ - * And perform these minor changes: + * Create an account for `IBM's Quantum Experience `_ + * And perform these minor changes: - .. literalinclude:: ../examples/quantum_random_numbers_ibm.py - :diff: ../examples/quantum_random_numbers.py - :tab-width: 2 + .. literalinclude:: ../examples/quantum_random_numbers_ibm.py + :diff: ../examples/quantum_random_numbers.py + :tab-width: 2 @@ -56,30 +56,30 @@ What she can do is use quantum teleportation to achieve this task. Yet, this onl .. math:: - |A\rangle \otimes |B\rangle = \frac 1{\sqrt 2} \left( |0\rangle\otimes|0\rangle + |1\rangle\otimes|1\rangle \right) + |A\rangle \otimes |B\rangle = \frac 1{\sqrt 2} \left( |0\rangle\otimes|0\rangle + |1\rangle\otimes|1\rangle \right) They can create a Bell-pair using a very simple circuit which first applies a Hadamard gate to the first qubit, and then flips the second qubit conditional on the first qubit being in :math:`|1\rangle`. The circuit diagram can be generated by calling the function .. literalinclude:: ../examples/teleport.py - :lines: 6,18-25 - :tab-width: 2 + :lines: 6,18-25 + :tab-width: 2 with a main compiler engine which has a CircuitDrawer back-end, i.e., .. literalinclude:: ../examples/bellpair_circuit.py - :tab-width: 2 + :tab-width: 2 The resulting LaTeX code can be compiled to produce the circuit diagram: .. code-block:: bash - $ python examples/bellpair_circuit.py > bellpair_circuit.tex - $ pdflatex bellpair_circuit.tex - + $ python examples/bellpair_circuit.py > bellpair_circuit.tex + $ pdflatex bellpair_circuit.tex + The output looks as follows: .. image:: images/bellpair_circuit.png - :align: center + :align: center Now, this Bell-pair can be used to achieve the quantum teleportation: Alice entangles her qubit with her share of the Bell-pair. Then, she measures both qubits; one in the Z-basis (Measure) and one in the Hadamard basis (Hadamard, then Measure). She then sends her measurement results to Bob who, depending on these outcomes, applies a Pauli-X or -Z gate. @@ -87,21 +87,21 @@ Now, this Bell-pair can be used to achieve the quantum teleportation: Alice enta The complete example looks as follows: .. literalinclude:: ../examples/teleport.py - :linenos: - :lines: 1-6,18-27,44-100 - :tab-width: 2 + :linenos: + :lines: 1-6,18-27,44-100 + :tab-width: 2 and the corresponding circuit can be generated using .. code-block:: bash - $ python examples/teleport_circuit.py > teleport_circuit.tex - $ pdflatex teleport_circuit.tex + $ python examples/teleport_circuit.py > teleport_circuit.tex + $ pdflatex teleport_circuit.tex which produces (after renaming of the qubits inside the tex-file): .. image:: images/teleport_circuit.png - :align: center + :align: center @@ -113,70 +113,70 @@ As a third example, consider Shor's algorithm for factoring, which for a given ( :math:`p_1\cdot p_2 = N` in polynomial time! This is a superpolynomial speed-up over the best known classical algorithm (which is the number field sieve) and enables the breaking of modern encryption schemes such as RSA on a future quantum computer. **A tiny bit of number theory** - There is a small amount of number theory involved, which reduces the problem of factoring to period-finding of the function + There is a small amount of number theory involved, which reduces the problem of factoring to period-finding of the function - .. math:: - f(x) = a^x\operatorname{mod} N + .. math:: + f(x) = a^x\operatorname{mod} N - for some `a` (relative prime to N, otherwise we get a factor right away anyway by calling `gcd(a,N)`). The period `r` for a function `f(x)` is the number for which :math:`f(x) = f(x+r)\forall x` holds. In this case, this means that :math:`a^x = a^{x+r}\;\; (\operatorname{mod} N)\;\forall x`. Therefore, :math:`a^r = 1 + qN` for some integer q and hence, :math:`a^r - 1 = (a^{r/2} - 1)(a^{r/2}+1) = qN`. This suggests that using the gcd on `N` and :math:`a^{r/2} \pm 1` we may find a factor of `N`! + for some `a` (relative prime to N, otherwise we get a factor right away anyway by calling `gcd(a,N)`). The period `r` for a function `f(x)` is the number for which :math:`f(x) = f(x+r)\forall x` holds. In this case, this means that :math:`a^x = a^{x+r}\;\; (\operatorname{mod} N)\;\forall x`. Therefore, :math:`a^r = 1 + qN` for some integer q and hence, :math:`a^r - 1 = (a^{r/2} - 1)(a^{r/2}+1) = qN`. This suggests that using the gcd on `N` and :math:`a^{r/2} \pm 1` we may find a factor of `N`! **Factoring on a quantum computer: An example** - At the heart of Shor's algorithm lies modular exponentiation of a classically known constant (denoted by `a` in the code) by a quantum superposition of numbers :math:`x`, i.e., + At the heart of Shor's algorithm lies modular exponentiation of a classically known constant (denoted by `a` in the code) by a quantum superposition of numbers :math:`x`, i.e., - .. math:: + .. math:: - |x\rangle|0\rangle \mapsto |x\rangle|a^x\operatorname{mod} N\rangle + |x\rangle|0\rangle \mapsto |x\rangle|a^x\operatorname{mod} N\rangle - Using :math:`N=15` and :math:`a=2`, and applying this operation to the uniform superposition over all :math:`x` leads to the superposition (modulo renormalization) + Using :math:`N=15` and :math:`a=2`, and applying this operation to the uniform superposition over all :math:`x` leads to the superposition (modulo renormalization) - .. math:: + .. math:: - |0\rangle|1\rangle + |1\rangle|2\rangle + |2\rangle|4\rangle + |3\rangle|8\rangle + |4\rangle|1\rangle + |5\rangle|2\rangle + |6\rangle|4\rangle + \cdots + |0\rangle|1\rangle + |1\rangle|2\rangle + |2\rangle|4\rangle + |3\rangle|8\rangle + |4\rangle|1\rangle + |5\rangle|2\rangle + |6\rangle|4\rangle + \cdots - In Shor's algorithm, the second register will not be touched again before the end of the quantum program, which means it might as well be measured now. Let's assume we measure 2; this collapses the state above to + In Shor's algorithm, the second register will not be touched again before the end of the quantum program, which means it might as well be measured now. Let's assume we measure 2; this collapses the state above to - .. math:: + .. math:: - |1\rangle|2\rangle + |5\rangle|2\rangle + |9\rangle|2\rangle + \cdots + |1\rangle|2\rangle + |5\rangle|2\rangle + |9\rangle|2\rangle + \cdots - The period of `a` modulo `N` can now be read off. On a quantum computer, this information can be accessed by applying an inverse quantum Fourier transform to the x-register, followed by a measurement of x. + The period of `a` modulo `N` can now be read off. On a quantum computer, this information can be accessed by applying an inverse quantum Fourier transform to the x-register, followed by a measurement of x. **Implementation** - There is an implementation of Shor's algorithm in the examples folder. It uses the implementation by Beauregard, `arxiv:0205095 `_ to factor an n-bit number using 2n+3 qubits. In this implementation, the modular exponentiation is carried out using modular multiplication and shift. Furthermore it uses the semi-classical quantum Fourier transform [see `arxiv:9511007 `_]: Pulling the final measurement of the `x`-register through the final inverse quantum Fourier transform allows to run the 2n modular multiplications serially, which keeps one from having to store the 2n qubits of x. - - Let's run it using the ProjectQ simulator: - - .. code-block:: text - - $ python3 examples/shor.py - - projectq - -------- - Implementation of Shor's algorithm. - Number to factor: 15 - - Factoring N = 15: 00000001 - - Factors found :-) : 3 * 5 = 15 - - Simulating Shor's algorithm at the level of single-qubit gates and CNOTs already takes quite a bit of time for larger numbers than 15. To turn on our **emulation feature**, which does not decompose the modular arithmetic to low-level gates, but carries it out directly instead, we can change the line - - .. literalinclude:: ../examples/shor.py - :lineno-start: 86 - :lines: 86-99 - :emphasize-lines: 8 - :linenos: - :tab-width: 2 - - in examples/shor.py to `return True`. This allows to factor, e.g. :math:`N=4,028,033` in under 3 minutes on a regular laptop! - - The most important part of the code is - - .. literalinclude:: ../examples/shor.py - :lines: 50-69 - :lineno-start: 50 - :linenos: - :dedent: 1 - :tab-width: 2 - - which executes the 2n modular multiplications conditioned on a control qubit `ctrl_qubit` in a uniform superposition of 0 and 1. The control qubit is then measured after performing the semi-classical inverse quantum Fourier transform and the measurement outcome is saved in the list `measurements`, followed by a reset of the control qubit to state 0. + There is an implementation of Shor's algorithm in the examples folder. It uses the implementation by Beauregard, `arxiv:0205095 `_ to factor an n-bit number using 2n+3 qubits. In this implementation, the modular exponentiation is carried out using modular multiplication and shift. Furthermore it uses the semi-classical quantum Fourier transform [see `arxiv:9511007 `_]: Pulling the final measurement of the `x`-register through the final inverse quantum Fourier transform allows to run the 2n modular multiplications serially, which keeps one from having to store the 2n qubits of x. + + Let's run it using the ProjectQ simulator: + + .. code-block:: text + + $ python3 examples/shor.py + + projectq + -------- + Implementation of Shor's algorithm. + Number to factor: 15 + + Factoring N = 15: 00000001 + + Factors found :-) : 3 * 5 = 15 + + Simulating Shor's algorithm at the level of single-qubit gates and CNOTs already takes quite a bit of time for larger numbers than 15. To turn on our **emulation feature**, which does not decompose the modular arithmetic to low-level gates, but carries it out directly instead, we can change the line + + .. literalinclude:: ../examples/shor.py + :lineno-start: 86 + :lines: 86-99 + :emphasize-lines: 8 + :linenos: + :tab-width: 2 + + in examples/shor.py to `return True`. This allows to factor, e.g. :math:`N=4,028,033` in under 3 minutes on a regular laptop! + + The most important part of the code is + + .. literalinclude:: ../examples/shor.py + :lines: 50-69 + :lineno-start: 50 + :linenos: + :dedent: 1 + :tab-width: 2 + + which executes the 2n modular multiplications conditioned on a control qubit `ctrl_qubit` in a uniform superposition of 0 and 1. The control qubit is then measured after performing the semi-classical inverse quantum Fourier transform and the measurement outcome is saved in the list `measurements`, followed by a reset of the control qubit to state 0. diff --git a/docs/index.rst b/docs/index.rst index e75130022..0c3f5ec4d 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,20 +14,20 @@ The **four core principles** of this open-source effort are Please cite - * Damian S. Steiger, Thomas Häner, and Matthias Troyer "ProjectQ: An Open Source Software Framework for Quantum Computing" `Quantum 2, 49 (2018) `__ (published on `arXiv `__ on 23 Dec 2016) - * Thomas Häner, Damian S. Steiger, Krysta M. Svore, and Matthias Troyer "A Software Methodology for Compiling Quantum Programs" `Quantum Sci. Technol. 3 (2018) 020501 `__ (published on `arXiv `__ on 5 Apr 2016) + * Damian S. Steiger, Thomas Häner, and Matthias Troyer "ProjectQ: An Open Source Software Framework for Quantum Computing" `Quantum 2, 49 (2018) `__ (published on `arXiv `__ on 23 Dec 2016) + * Thomas Häner, Damian S. Steiger, Krysta M. Svore, and Matthias Troyer "A Software Methodology for Compiling Quantum Programs" `Quantum Sci. Technol. 3 (2018) 020501 `__ (published on `arXiv `__ on 5 Apr 2016) Contents - * :ref:`tutorial`: Tutorial containing instructions on how to get started with ProjectQ. - * :ref:`examples`: Example implementations of few quantum algorithms - * :ref:`code_doc`: The code documentation of ProjectQ. + * :ref:`tutorial`: Tutorial containing instructions on how to get started with ProjectQ. + * :ref:`examples`: Example implementations of few quantum algorithms + * :ref:`code_doc`: The code documentation of ProjectQ. .. toctree:: - :maxdepth: 2 - :hidden: - - tutorials - examples - projectq + :maxdepth: 2 + :hidden: + + tutorials + examples + projectq diff --git a/docs/make.bat b/docs/make.bat index 7d0ce4919..8058b20d2 100755 --- a/docs/make.bat +++ b/docs/make.bat @@ -3,50 +3,50 @@ REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build + set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. epub3 to make an epub3 - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - echo. dummy to check syntax errors of document sources - goto end + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. epub3 to make an epub3 + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + echo. dummy to check syntax errors of document sources + goto end ) if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end ) @@ -60,222 +60,222 @@ goto sphinx_ok set SPHINXBUILD=python -m sphinx.__init__ %SPHINXBUILD% 2> nul if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 ) :sphinx_ok if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end ) if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end ) if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end ) if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end ) if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end ) if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. - goto end + goto end ) if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\projectq.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\projectq.ghc - goto end + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\projectq.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\projectq.ghc + goto end ) if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end ) if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end ) if "%1" == "epub3" ( - %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. - goto end + %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. + goto end ) if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end ) if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end ) if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end ) if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end ) if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end ) if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end ) if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end ) if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end ) if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. - goto end + goto end ) if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. - goto end + goto end ) if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ results in %BUILDDIR%/coverage/python.txt. - goto end + goto end ) if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end ) if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end ) if "%1" == "dummy" ( - %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. Dummy builder generates no files. - goto end + %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. Dummy builder generates no files. + goto end ) :end diff --git a/docs/package_description.py b/docs/package_description.py index 9980e4235..a782a1aa8 100644 --- a/docs/package_description.py +++ b/docs/package_description.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import inspect import sys import os @@ -6,13 +7,15 @@ class PackageDescription(object): package_list = [] - def __init__(self, - pkg_name, - desc='', - module_special_members='__init__', - submodule_special_members='', - submodules_desc='', - helper_submodules=None): + def __init__( + self, + pkg_name, + desc='', + module_special_members='__init__', + submodule_special_members='', + submodules_desc='', + helper_submodules=None, + ): """ Args: name (str): Name of ProjectQ module @@ -43,16 +46,19 @@ def __init__(self, self.helper_submodules = helper_submodules module_root = os.path.dirname(self.module.__file__) - sub = [(name, obj) for name, obj in inspect.getmembers( - self.module, lambda obj: inspect.ismodule(obj) and hasattr( - obj, '__file__') and module_root in obj.__file__) - if pkg_name[0] != '_'] + sub = [ + (name, obj) + for name, obj in inspect.getmembers( + self.module, + lambda obj: inspect.ismodule(obj) and hasattr(obj, '__file__') and module_root in obj.__file__, + ) + if pkg_name[0] != '_' + ] self.subpackages = [] self.submodules = [] for name, obj in sub: - if '{}.{}'.format(self.name, - name) in PackageDescription.package_list: + if '{}.{}'.format(self.name, name) in PackageDescription.package_list: self.subpackages.append((name, obj)) else: self.submodules.append((name, obj)) @@ -60,11 +66,18 @@ def __init__(self, self.subpackages.sort(key=lambda x: x[0].lower()) self.submodules.sort(key=lambda x: x[0].lower()) - self.members = [(name, obj) for name, obj in inspect.getmembers( - self.module, lambda obj: - (inspect.isclass(obj) or inspect.isfunction(obj) or isinstance( - obj, (int, float, tuple, list, dict, set, frozenset, str)))) - if name[0] != '_'] + self.members = [ + (name, obj) + for name, obj in inspect.getmembers( + self.module, + lambda obj: ( + inspect.isclass(obj) + or inspect.isfunction(obj) + or isinstance(obj, (int, float, tuple, list, dict, set, frozenset, str)) + ), + ) + if name[0] != '_' + ] self.members.sort(key=lambda x: x[0].lower()) def get_ReST(self): @@ -95,13 +108,11 @@ def get_ReST(self): new_lines.append('') if self.submodules: for name, _ in self.submodules: - new_lines.append('\tprojectq.{}.{}'.format( - self.name, name)) + new_lines.append('\tprojectq.{}.{}'.format(self.name, name)) new_lines.append('') if self.members: for name, _ in self.members: - new_lines.append('\tprojectq.{}.{}'.format( - self.name, name)) + new_lines.append('\tprojectq.{}.{}'.format(self.name, name)) new_lines.append('') if self.submodules: @@ -116,20 +127,17 @@ def get_ReST(self): new_lines.append('.. autosummary::') new_lines.append('') for name, _ in self.submodules: - new_lines.append(' projectq.{}.{}'.format( - self.name, name)) + new_lines.append(' projectq.{}.{}'.format(self.name, name)) new_lines.append('') for name, _ in self.submodules: new_lines.append(name) new_lines.append('^' * len(new_lines[-1])) new_lines.append('') - new_lines.append('.. automodule:: projectq.{}.{}'.format( - self.name, name)) + new_lines.append('.. automodule:: projectq.{}.{}'.format(self.name, name)) new_lines.append(' :members:') if self.submodule_special_members: - new_lines.append(' :special-members: {}'.format( - self.submodule_special_members)) + new_lines.append(' :special-members: {}'.format(self.submodule_special_members)) new_lines.append(' :undoc-members:') new_lines.append('') @@ -139,8 +147,7 @@ def get_ReST(self): new_lines.append('.. automodule:: projectq.{}'.format(self.name)) new_lines.append(' :members:') new_lines.append(' :undoc-members:') - new_lines.append(' :special-members: {}'.format( - self.module_special_members)) + new_lines.append(' :special-members: {}'.format(self.module_special_members)) new_lines.append(' :imported-members:') new_lines.append('') @@ -152,8 +159,7 @@ def get_ReST(self): new_lines.append(title) new_lines.append('^' * len(title)) new_lines.append('') - new_lines.append('.. automodule:: projectq.{}.{}'.format( - self.name, name)) + new_lines.append('.. automodule:: projectq.{}.{}'.format(self.name, name)) for param in params: new_lines.append(' {}'.format(param)) new_lines.append('') diff --git a/docs/projectq.rst b/docs/projectq.rst index 16a948655..35a6f7285 100755 --- a/docs/projectq.rst +++ b/docs/projectq.rst @@ -10,7 +10,7 @@ For a detailed documentation of a subpackage or module, click on its name below: .. toctree:: :maxdepth: 1 :titlesonly: - + _doc_gen/projectq.backends _doc_gen/projectq.cengines _doc_gen/projectq.libs @@ -18,5 +18,3 @@ For a detailed documentation of a subpackage or module, click on its name below: _doc_gen/projectq.ops _doc_gen/projectq.setups _doc_gen/projectq.types - - diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..82133027c --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,2 @@ +sphinx +sphinx_rtd_theme diff --git a/docs/tutorials.rst b/docs/tutorials.rst index cc7800ed9..13cb08624 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -4,7 +4,7 @@ Tutorial ======== .. toctree:: - :maxdepth: 2 + :maxdepth: 2 Getting started --------------- @@ -13,36 +13,36 @@ To start using ProjectQ, simply run .. code-block:: bash - python -m pip install --user projectq + python -m pip install --user projectq or, alternatively, `clone/download `_ this repo (e.g., to your /home directory) and run .. code-block:: bash - cd /home/projectq - python -m pip install --user . + cd /home/projectq + python -m pip install --user . ProjectQ comes with a high-performance quantum simulator written in C++. Please see the detailed OS specific installation instructions below to make sure that you are installing the fastest version. .. note:: - The setup will try to build a C++-Simulator, which is much faster than the Python implementation. If the C++ compilation were to fail, the setup will install a pure Python implementation of the simulator instead. The Python simulator should work fine for small examples (e.g., running Shor's algorithm for factoring 15 or 21). + The setup will try to build a C++-Simulator, which is much faster than the Python implementation. If the C++ compilation were to fail, the setup will install a pure Python implementation of the simulator instead. The Python simulator should work fine for small examples (e.g., running Shor's algorithm for factoring 15 or 21). - If you want to skip the installation of the C++-Simulator altogether, you can define the ``DISABLE_PROJECTQ_CEXT`` environment variable to avoid any compilation steps. + If you want to skip the installation of the C++-Simulator altogether, you can define the ``DISABLE_PROJECTQ_CEXT`` environment variable to avoid any compilation steps. .. note:: - If building the C++-Simulator does not work out of the box, consider specifying a different compiler. For example: - - .. code-block:: bash - - env CC=g++-5 python -m pip install --user projectq + If building the C++-Simulator does not work out of the box, consider specifying a different compiler. For example: - Please note that the compiler you specify must support at leaste **C++11**! + .. code-block:: bash + + env CC=g++-5 python -m pip install --user projectq + + Please note that the compiler you specify must support at leaste **C++11**! .. note:: - Please use pip version v6.1.0 or higher as this ensures that dependencies are installed in the `correct order `_. + Please use pip version v6.1.0 or higher as this ensures that dependencies are installed in the `correct order `_. .. note:: - ProjectQ should be installed on each computer individually as the C++ simulator compilation creates binaries which are optimized for the specific hardware on which it is being installed (potentially using our AVX version and `-march=native`). Therefore, sharing the same ProjectQ installation across different hardware may cause some problems. + ProjectQ should be installed on each computer individually as the C++ simulator compilation creates binaries which are optimized for the specific hardware on which it is being installed (potentially using our AVX version and `-march=native`). Therefore, sharing the same ProjectQ installation across different hardware may cause some problems. **Install AWS Braket Backend requirement** @@ -50,7 +50,7 @@ AWS Braket Backend requires the use of the official AWS SDK for Python, Boto3. T .. code-block:: bash - python -m pip install --user projectq[braket] + python -m pip install --user projectq[braket] Detailed instructions and OS-specific hints @@ -58,158 +58,158 @@ Detailed instructions and OS-specific hints **Ubuntu**: - After having installed the build tools (for g++): - - .. code-block:: bash - - sudo apt-get install build-essential - - You only need to install Python (and the package manager). For version 3, run - - .. code-block:: bash - - sudo apt-get install python3 python3-pip - - When you then run - - .. code-block:: bash - - sudo python3 -m pip install --user projectq - - all dependencies (such as numpy and pybind11) should be installed automatically. + After having installed the build tools (for g++): + + .. code-block:: bash + + sudo apt-get install build-essential + + You only need to install Python (and the package manager). For version 3, run + + .. code-block:: bash + + sudo apt-get install python3 python3-pip + + When you then run + + .. code-block:: bash + + sudo python3 -m pip install --user projectq + + all dependencies (such as numpy and pybind11) should be installed automatically. **ArchLinux/Manjaro**: Make sure that you have a C/C++ compiler installed: - .. code-block:: bash - - sudo pacman -Syu gcc - - You only need to install Python (and the package manager). For version 3, run - - .. code-block:: bash - - sudo pacman -Syu python python-pip - - When you then run - - .. code-block:: bash - - sudo python3 -m pip install --user projectq - - all dependencies (such as numpy and pybind11) should be installed automatically. + .. code-block:: bash + + sudo pacman -Syu gcc + + You only need to install Python (and the package manager). For version 3, run + + .. code-block:: bash + + sudo pacman -Syu python python-pip + + When you then run + + .. code-block:: bash + + sudo python3 -m pip install --user projectq + + all dependencies (such as numpy and pybind11) should be installed automatically. **Windows**: - It is easiest to install a pre-compiled version of Python, including numpy and many more useful packages. One way to do so is using, e.g., the Python 3.7 installers from `python.org `_ or `ANACONDA `_. Installing ProjectQ right away will succeed for the (slow) Python simulator. For a compiled version of the simulator, install the Visual C++ Build Tools and the Microsoft Windows SDK prior to doing a pip install. The built simulator will not support multi-threading due to the limited OpenMP support of the Visual Studio compiler. + It is easiest to install a pre-compiled version of Python, including numpy and many more useful packages. One way to do so is using, e.g., the Python 3.7 installers from `python.org `_ or `ANACONDA `_. Installing ProjectQ right away will succeed for the (slow) Python simulator. For a compiled version of the simulator, install the Visual C++ Build Tools and the Microsoft Windows SDK prior to doing a pip install. The built simulator will not support multi-threading due to the limited OpenMP support of the Visual Studio compiler. + + If the Python executable is added to your PATH (option normally suggested at the end of the Python installation procedure), you can then open a cmdline window (WIN + R, type "cmd" and click *OK*) and enter the following in order to install ProjectQ: + + .. code-block:: batch - If the Python executable is added to your PATH (option normally suggested at the end of the Python installation procedure), you can then open a cmdline window (WIN + R, type "cmd" and click *OK*) and enter the following in order to install ProjectQ: + python -m pip install --user projectq - .. code-block:: batch - - python -m pip install --user projectq - - Should you want to run multi-threaded simulations, you can install a compiler which supports newer OpenMP versions, such as MinGW GCC and then manually build the C++ simulator with OpenMP enabled. + Should you want to run multi-threaded simulations, you can install a compiler which supports newer OpenMP versions, such as MinGW GCC and then manually build the C++ simulator with OpenMP enabled. **macOS**: Similarly to the other platforms, installing ProjectQ without the C++ simulator is really easy: - .. code-block:: bash + .. code-block:: bash - python3 -m pip install --user projectq + python3 -m pip install --user projectq - - In order to install the fast C++ simulator, we require that a C++ compiler is installed on your system. There are essentially three options you can choose from: - 1. Using the compiler provided by Apple through the XCode command line tools. - 2. Using Homebrew - 3. Using MacPorts + In order to install the fast C++ simulator, we require that a C++ compiler is installed on your system. There are essentially three options you can choose from: - For both options 2 and 3, you will be required to first install the XCode command line tools + 1. Using the compiler provided by Apple through the XCode command line tools. + 2. Using Homebrew + 3. Using MacPorts + For both options 2 and 3, you will be required to first install the XCode command line tools - **Apple XCode command line tool** - Install the XCode command line tools by opening a terminal window and running the following command: + **Apple XCode command line tool** - .. code-block:: bash + Install the XCode command line tools by opening a terminal window and running the following command: - xcode-select --install - - Next, you will need to install Python and pip. See options 2 and 3 for information on how to install a newer python version with either Homebrew or MacPorts. Here, we are using the standard python which is preinstalled with macOS. Pip can be installed by: + .. code-block:: bash - .. code-block:: bash + xcode-select --install - sudo easy_install pip + Next, you will need to install Python and pip. See options 2 and 3 for information on how to install a newer python version with either Homebrew or MacPorts. Here, we are using the standard python which is preinstalled with macOS. Pip can be installed by: - Now, you can install ProjectQ with the C++ simulator using the standard command: + .. code-block:: bash - .. code-block:: bash + sudo easy_install pip - python3 -m pip install --user projectq + Now, you can install ProjectQ with the C++ simulator using the standard command: - Note that the compiler provided by Apple is currently not able to compile ProjectQ's multi-threaded code. + .. code-block:: bash + + python3 -m pip install --user projectq + + Note that the compiler provided by Apple is currently not able to compile ProjectQ's multi-threaded code. **Homebrew** - First install the XCode command line tools. Then install Homebrew with the following command: + First install the XCode command line tools. Then install Homebrew with the following command: + + .. code-block:: bash - .. code-block:: bash + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" - /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + Then proceed to install Python as well as a C/C++ compiler (note: gcc installed via Homebrew may lead to some issues): - Then proceed to install Python as well as a C/C++ compiler (note: gcc installed via Homebrew may lead to some issues): + .. code-block:: bash - .. code-block:: bash + brew install python llvm - brew install python llvm - - You should now be able to install ProjectQ with the C++ simulator using the following command: + You should now be able to install ProjectQ with the C++ simulator using the following command: - .. code-block:: bash + .. code-block:: bash - env P=/usr/local/opt/llvm/bin CC=$P/clang CXX=$P/clang++ python3 -m pip install --user projectq + env P=/usr/local/opt/llvm/bin CC=$P/clang CXX=$P/clang++ python3 -m pip install --user projectq - **MacPorts** + **MacPorts** - Visit `macports.org `_ and install the latest version that corresponds to your operating system's version. Afterwards, open a new terminal window. + Visit `macports.org `_ and install the latest version that corresponds to your operating system's version. Afterwards, open a new terminal window. - Then, use macports to install Python 3.7 by entering the following command + Then, use macports to install Python 3.7 by entering the following command - .. code-block:: bash + .. code-block:: bash - sudo port install python37 + sudo port install python37 - It might show a warning that if you intend to use python from the terminal. In this case, you should also install + It might show a warning that if you intend to use python from the terminal. In this case, you should also install - .. code-block:: bash + .. code-block:: bash - sudo port install py37-gnureadline + sudo port install py37-gnureadline - Install pip by + Install pip by - .. code-block:: bash + .. code-block:: bash - sudo port install py37-pip + sudo port install py37-pip - Next, we can install ProjectQ with the high performance simulator written in C++. First, we will need to install a suitable compiler with support for **C++11**, OpenMP, and instrinsics. The best option is to install clang 9.0 also using macports (note: gcc installed via macports does not work). + Next, we can install ProjectQ with the high performance simulator written in C++. First, we will need to install a suitable compiler with support for **C++11**, OpenMP, and instrinsics. The best option is to install clang 9.0 also using macports (note: gcc installed via macports does not work). - .. code-block:: bash + .. code-block:: bash - sudo port install clang-9.0 + sudo port install clang-9.0 - ProjectQ is now installed by: + ProjectQ is now installed by: - .. code-block:: bash + .. code-block:: bash - env CC=clang-mp-9.0 env CXX=clang++-mp-9.0 /opt/local/bin/python3.7 -m pip install --user projectq + env CC=clang-mp-9.0 env CXX=clang++-mp-9.0 /opt/local/bin/python3.7 -m pip install --user projectq The ProjectQ syntax @@ -221,7 +221,7 @@ For example, consider applying an x-rotation by an angle `theta` to a qubit. In .. code-block:: python - Rx(theta) | qubit + Rx(theta) | qubit whereas the corresponding notation in physics would be @@ -236,16 +236,16 @@ To check out the ProjectQ syntax in action and to see whether the installation w .. code-block:: python - from projectq import MainEngine # import the main compiler engine - from projectq.ops import H, Measure # import the operations we want to perform (Hadamard and measurement) - - eng = MainEngine() # create a default compiler (the back-end is a simulator) - qubit = eng.allocate_qubit() # allocate 1 qubit - - H | qubit # apply a Hadamard gate - Measure | qubit # measure the qubit - - eng.flush() # flush all gates (and execute measurements) - print("Measured {}".format(int(qubit))) # output measurement result + from projectq import MainEngine # import the main compiler engine + from projectq.ops import H, Measure # import the operations we want to perform (Hadamard and measurement) + + eng = MainEngine() # create a default compiler (the back-end is a simulator) + qubit = eng.allocate_qubit() # allocate 1 qubit + + H | qubit # apply a Hadamard gate + Measure | qubit # measure the qubit + + eng.flush() # flush all gates (and execute measurements) + print("Measured {}".format(int(qubit))) # output measurement result Which creates random bits (0 or 1). diff --git a/examples/aqt.py b/examples/aqt.py index c4cadf17f..e2fb6af12 100644 --- a/examples/aqt.py +++ b/examples/aqt.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import matplotlib.pyplot as plt import getpass @@ -44,24 +45,24 @@ def run_entangle(eng, num_qubits=3): if __name__ == "__main__": - #devices available to subscription: + # devices available to subscription: # aqt_simulator (11 qubits) # aqt_simulator_noise (11 qubits) # aqt_device (4 qubits) - # + # # To get a subscription, create a profile at : # https://gateway-portal.aqt.eu/ - # - device = None # replace by the AQT device name you want to use + # + device = None # replace by the AQT device name you want to use token = None # replace by the token given by AQT if token is None: token = getpass.getpass(prompt='AQT token > ') if device is None: device = getpass.getpass(prompt='AQT device > ') # create main compiler engine for the AQT back-end - eng = MainEngine(AQTBackend(use_hardware=True, token=token, num_runs=200, - verbose=False, device=device), - engine_list=projectq.setups.aqt.get_engine_list( - token=token, device=device)) + eng = MainEngine( + AQTBackend(use_hardware=True, token=token, num_runs=200, verbose=False, device=device), + engine_list=projectq.setups.aqt.get_engine_list(token=token, device=device), + ) # run the circuit and print the result print(run_entangle(eng)) diff --git a/examples/bellpair_circuit.py b/examples/bellpair_circuit.py index 96c001ffe..fff144602 100755 --- a/examples/bellpair_circuit.py +++ b/examples/bellpair_circuit.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import matplotlib.pyplot as plt from projectq import MainEngine @@ -9,7 +10,7 @@ # create a main compiler engine drawing_engine = CircuitDrawer() -eng = MainEngine(engine_list = get_engine_list() + [drawing_engine]) +eng = MainEngine(engine_list=get_engine_list() + [drawing_engine]) qb0, qb1 = create_bell_pair(eng) diff --git a/examples/gate_zoo.py b/examples/gate_zoo.py index bce118994..6abe51c00 100644 --- a/examples/gate_zoo.py +++ b/examples/gate_zoo.py @@ -1,10 +1,37 @@ +# -*- coding: utf-8 -*- import os import sys -import projectq.setups.default from projectq import MainEngine from projectq.backends import CircuitDrawer -from projectq.ops import * +from projectq.ops import ( + X, + Y, + Z, + Rx, + Ry, + Rz, + Ph, + S, + T, + H, + Toffoli, + Barrier, + Swap, + SqrtSwap, + SqrtX, + C, + CNOT, + Entangle, + QFT, + TimeEvolution, + QubitOperator, + BasicMathGate, + Measure, + All, + Tensor, + get_inverse, +) def zoo_profile(): @@ -23,14 +50,32 @@ def zoo_profile(): def add(x, y): return x, y + 1 + zoo = [ - (X, 3), (Y, 2), (Z, 0), (Rx(0.5), 2), (Ry(0.5), 1), - (Rz(0.5), 1), (Ph(0.5), 0), (S, 3), (T, 2), (H, 1), - (Toffoli, (0, 1, 2)), (Barrier, None), (Swap, (0, 3)), - (SqrtSwap, (0, 1)), (get_inverse(SqrtSwap), (2, 3)), - (SqrtX, 2), (C(get_inverse(SqrtX)), (0, 2)), (C(Ry(0.5)), (2, 3)), - (CNOT, (2, 1)), (Entangle, None), (te_gate, None), (QFT, None), - (Tensor(H), None), (BasicMathGate(add), (2, 3)), + (X, 3), + (Y, 2), + (Z, 0), + (Rx(0.5), 2), + (Ry(0.5), 1), + (Rz(0.5), 1), + (Ph(0.5), 0), + (S, 3), + (T, 2), + (H, 1), + (Toffoli, (0, 1, 2)), + (Barrier, None), + (Swap, (0, 3)), + (SqrtSwap, (0, 1)), + (get_inverse(SqrtSwap), (2, 3)), + (SqrtX, 2), + (C(get_inverse(SqrtX)), (0, 2)), + (C(Ry(0.5)), (2, 3)), + (CNOT, (2, 1)), + (Entangle, None), + (te_gate, None), + (QFT, None), + (Tensor(H), None), + (BasicMathGate(add), (2, 3)), (All(Measure), None), ] diff --git a/examples/grover.py b/examples/grover.py index b9823efb8..d845d148a 100755 --- a/examples/grover.py +++ b/examples/grover.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import math from projectq import MainEngine @@ -25,7 +26,7 @@ def run_grover(eng, n, oracle): All(H) | x # number of iterations we have to run: - num_it = int(math.pi/4.*math.sqrt(1 << n)) + num_it = int(math.pi / 4.0 * math.sqrt(1 << n)) # prepare the oracle output qubit (the one that is flipped to indicate the # solution. start in state 1/sqrt(2) * (|0> - |1>) s.t. a bit-flip turns diff --git a/examples/hws4.py b/examples/hws4.py index 12178fda5..3b0ab9a0b 100644 --- a/examples/hws4.py +++ b/examples/hws4.py @@ -1,12 +1,15 @@ +# -*- coding: utf-8 -*- from projectq.cengines import MainEngine from projectq.ops import All, H, X, Measure from projectq.meta import Compute, Uncompute from projectq.libs.revkit import PhaseOracle + # phase function def f(a, b, c, d): return (a and b) ^ (c and d) + eng = MainEngine() x1, x2, x3, x4 = qubits = eng.allocate_qureg(4) diff --git a/examples/hws6.py b/examples/hws6.py index c8becc20c..6a17b532f 100644 --- a/examples/hws6.py +++ b/examples/hws6.py @@ -1,21 +1,24 @@ +# -*- coding: utf-8 -*- from projectq.cengines import MainEngine -from projectq.ops import All, H, X, CNOT, Measure +from projectq.ops import All, H, X, Measure from projectq.meta import Compute, Uncompute, Dagger from projectq.libs.revkit import PhaseOracle, PermutationOracle import revkit + # phase function def f(a, b, c, d, e, f): return (a and b) ^ (c and d) ^ (e and f) + # permutation pi = [0, 2, 3, 5, 7, 1, 4, 6] eng = MainEngine() qubits = eng.allocate_qureg(6) x = qubits[::2] # qubits on odd lines -y = qubits[1::2] # qubits on even lines +y = qubits[1::2] # qubits on even lines # circuit with Compute(eng): @@ -27,7 +30,7 @@ def f(a, b, c, d, e, f): with Compute(eng): with Dagger(eng): - PermutationOracle(pi, synth = revkit.dbs) | x + PermutationOracle(pi, synth=revkit.dbs) | x PhaseOracle(f) | qubits Uncompute(eng) diff --git a/examples/ibm.py b/examples/ibm.py index 33427adc9..6bc2913e9 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import matplotlib.pyplot as plt import getpass @@ -44,7 +45,7 @@ def run_entangle(eng, num_qubits=3): if __name__ == "__main__": - #devices commonly available : + # devices commonly available : # ibmq_16_melbourne (15 qubit) # ibmq_essex (5 qubit) # ibmq_qasm_simulator (32 qubits) @@ -53,16 +54,16 @@ def run_entangle(eng, num_qubits=3): # To get a token, create a profile at: # https://quantum-computing.ibm.com/ # - device = None # replace by the IBM device name you want to use + 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: device = getpass.getpass(prompt='IBM device > ') # create main compiler engine for the IBM back-end - 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)) + 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)) diff --git a/examples/quantum_random_numbers.py b/examples/quantum_random_numbers.py index 9ccb679c8..76d51e178 100755 --- a/examples/quantum_random_numbers.py +++ b/examples/quantum_random_numbers.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from projectq.ops import H, Measure from projectq import MainEngine diff --git a/examples/quantum_random_numbers_ibm.py b/examples/quantum_random_numbers_ibm.py index a8289a68d..15bf9b80c 100755 --- a/examples/quantum_random_numbers_ibm.py +++ b/examples/quantum_random_numbers_ibm.py @@ -1,11 +1,11 @@ +# -*- coding: utf-8 -*- import projectq.setups.ibm from projectq.ops import H, Measure from projectq import MainEngine from projectq.backends import IBMBackend # create a main compiler engine -eng = MainEngine(IBMBackend(), - engine_list=projectq.setups.ibm.get_engine_list()) +eng = MainEngine(IBMBackend(), engine_list=projectq.setups.ibm.get_engine_list()) # allocate one qubit q1 = eng.allocate_qubit() diff --git a/examples/shor.py b/examples/shor.py index 949f804c1..c3066e780 100755 --- a/examples/shor.py +++ b/examples/shor.py @@ -1,9 +1,11 @@ +# -*- coding: utf-8 -*- from __future__ import print_function import math import random import sys from fractions import Fraction + try: from math import gcd except ImportError: @@ -14,14 +16,17 @@ import projectq.libs.math import projectq.setups.decompositions from projectq.backends import Simulator, ResourceCounter -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - InstructionFilter, LocalOptimizer, - MainEngine, TagRemover) -from projectq.libs.math import (AddConstant, AddConstantModN, - MultiplyByConstantModN) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + InstructionFilter, + LocalOptimizer, + MainEngine, + TagRemover, +) +from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN from projectq.meta import Control -from projectq.ops import (All, BasicMathGate, get_inverse, H, Measure, QFT, R, - Swap, X) +from projectq.ops import All, BasicMathGate, get_inverse, H, Measure, QFT, R, Swap, X def run_shor(eng, N, a, verbose=False): @@ -57,7 +62,7 @@ def run_shor(eng, N, a, verbose=False): # perform inverse QFT --> Rotations conditioned on previous outcomes for i in range(k): if measurements[i]: - R(-math.pi/(1 << (k - i))) | ctrl_qubit + R(-math.pi / (1 << (k - i))) | ctrl_qubit H | ctrl_qubit # and measure @@ -73,11 +78,10 @@ def run_shor(eng, N, a, verbose=False): All(Measure) | x # turn the measured values into a number in [0,1) - y = sum([(measurements[2 * n - 1 - i]*1. / (1 << (i + 1))) - for i in range(2 * n)]) + y = sum([(measurements[2 * n - 1 - i] * 1.0 / (1 << (i + 1))) for i in range(2 * n)]) # continued fraction expansion to get denominator (the period?) - r = Fraction(y).limit_denominator(N-1).denominator + r = Fraction(y).limit_denominator(N - 1).denominator # return the (potential) period return r @@ -102,31 +106,33 @@ def high_level_gates(eng, cmd): if __name__ == "__main__": # build compilation engine list resource_counter = ResourceCounter() - rule_set = DecompositionRuleSet(modules=[projectq.libs.math, - projectq.setups.decompositions]) - compilerengines = [AutoReplacer(rule_set), - InstructionFilter(high_level_gates), - TagRemover(), - LocalOptimizer(3), - AutoReplacer(rule_set), - TagRemover(), - LocalOptimizer(3), - resource_counter] + rule_set = DecompositionRuleSet(modules=[projectq.libs.math, projectq.setups.decompositions]) + compilerengines = [ + AutoReplacer(rule_set), + InstructionFilter(high_level_gates), + TagRemover(), + LocalOptimizer(3), + AutoReplacer(rule_set), + TagRemover(), + LocalOptimizer(3), + resource_counter, + ] # make the compiler and run the circuit on the simulator backend eng = MainEngine(Simulator(), compilerengines) # print welcome message and ask the user for the number to factor - print("\n\t\033[37mprojectq\033[0m\n\t--------\n\tImplementation of Shor" - "\'s algorithm.", end="") + print( + "\n\t\033[37mprojectq\033[0m\n\t--------\n\tImplementation of Shor" "\'s algorithm.", + end="", + ) N = int(input('\n\tNumber to factor: ')) print("\n\tFactoring N = {}: \033[0m".format(N), end="") # choose a base at random: - a = int(random.random()*N) + a = int(random.random() * N) if not gcd(a, N) == 1: - print("\n\n\t\033[92mOoops, we were lucky: Chose non relative prime" - " by accident :)") + print("\n\n\t\033[92mOoops, we were lucky: Chose non relative prime" " by accident :)") print("\tFactor: {}\033[0m".format(gcd(a, N))) else: # run the quantum subroutine @@ -138,14 +144,11 @@ def high_level_gates(eng, cmd): apowrhalf = pow(a, r >> 1, N) f1 = gcd(apowrhalf + 1, N) f2 = gcd(apowrhalf - 1, N) - if ((not f1 * f2 == N) and f1 * f2 > 1 and - int(1. * N / (f1 * f2)) * f1 * f2 == N): - f1, f2 = f1*f2, int(N/(f1*f2)) + if (not f1 * f2 == N) and f1 * f2 > 1 and int(1.0 * N / (f1 * f2)) * f1 * f2 == N: + f1, f2 = f1 * f2, int(N / (f1 * f2)) if f1 * f2 == N and f1 > 1 and f2 > 1: - print("\n\n\t\033[92mFactors found :-) : {} * {} = {}\033[0m" - .format(f1, f2, N)) + print("\n\n\t\033[92mFactors found :-) : {} * {} = {}\033[0m".format(f1, f2, N)) else: - print("\n\n\t\033[91mBad luck: Found {} and {}\033[0m".format(f1, - f2)) + print("\n\n\t\033[91mBad luck: Found {} and {}\033[0m".format(f1, f2)) print(resource_counter) # print resource usage diff --git a/examples/teleport.py b/examples/teleport.py index d5f24ef76..499767868 100755 --- a/examples/teleport.py +++ b/examples/teleport.py @@ -1,10 +1,11 @@ -from projectq.ops import All, CNOT, H, Measure, Rz, X, Z +# -*- coding: utf-8 -*- +from projectq.ops import CNOT, H, Measure, Rz, X, Z from projectq import MainEngine from projectq.meta import Dagger, Control def create_bell_pair(eng): - """ + r""" Returns a Bell-pair (two qubits in state :math:`|A\rangle \otimes |B \rangle = \frac 1{\sqrt 2} \left( |0\rangle\otimes|0\rangle + |1\rangle \otimes|1\rangle \right)`). diff --git a/examples/teleport_circuit.py b/examples/teleport_circuit.py index 6910d0582..bf1ff5e0d 100755 --- a/examples/teleport_circuit.py +++ b/examples/teleport_circuit.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from projectq import MainEngine from projectq.backends import CircuitDrawer diff --git a/projectq/__init__.py b/projectq/__init__.py index 8dff84e6c..d09243a4a 100755 --- a/projectq/__init__.py +++ b/projectq/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ ProjectQ - An open source software framework for quantum computing @@ -25,5 +25,4 @@ Shor's algorithm for factoring. """ -from ._version import __version__ from projectq.cengines import MainEngine diff --git a/projectq/_version.py b/projectq/_version.py deleted file mode 100755 index f66d9b474..000000000 --- a/projectq/_version.py +++ /dev/null @@ -1,16 +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. - -"""Define version number here and read it from setup.py automatically""" -__version__ = "0.5.2" diff --git a/projectq/backends/__init__.py b/projectq/backends/__init__.py index 0d65fbab8..ae6a2c3f4 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Contains back-ends for ProjectQ. diff --git a/projectq/backends/_aqt/__init__.py b/projectq/backends/_aqt/__init__.py index 391c1ff4a..08893cf17 100644 --- a/projectq/backends/_aqt/__init__.py +++ b/projectq/backends/_aqt/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_aqt/_aqt.py b/projectq/backends/_aqt/_aqt.py index 23ae5fbd7..df5a41a8c 100644 --- a/projectq/backends/_aqt/_aqt.py +++ b/projectq/backends/_aqt/_aqt.py @@ -1,4 +1,5 @@ -# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# -*- coding: utf-8 -*- +# Copyright 2020, 2021 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. @@ -18,8 +19,7 @@ from projectq.cengines import BasicEngine from projectq.meta import get_control_count, LogicalQubitIDTag -from projectq.ops import (Rx, Ry, Rxx, Measure, Allocate, Barrier, Deallocate, - FlushGate) +from projectq.ops import Rx, Ry, Rxx, Measure, Allocate, Barrier, Deallocate, FlushGate from ._aqt_http_client import send, retrieve @@ -38,10 +38,7 @@ def _format_counts(samples, length): counts[h_result] = 1 else: counts[h_result] += 1 - counts = { - k: v - for k, v in sorted(counts.items(), key=lambda item: item[0]) - } + counts = {k: v for k, v in sorted(counts.items(), key=lambda item: item[0])} return counts @@ -50,15 +47,18 @@ class AQTBackend(BasicEngine): The AQT Backend class, which stores the circuit, transforms it to the appropriate data format, and sends the circuit through the AQT API. """ - def __init__(self, - use_hardware=False, - num_runs=100, - verbose=False, - token='', - device='simulator', - num_retries=3000, - interval=1, - retrieve_execution=None): + + def __init__( + self, + use_hardware=False, + num_runs=100, + verbose=False, + token='', + device='simulator', + num_retries=3000, + interval=1, + retrieve_execution=None, + ): """ Initialize the Backend object. @@ -116,7 +116,7 @@ def is_available(self, cmd): return False def _reset(self): - """ Reset all temporary variables (after flush gate). """ + """Reset all temporary variables (after flush gate).""" self._clear = True self._measured_ids = [] @@ -163,7 +163,7 @@ def _store(self, cmd): angle = gate.angle / math.pi instruction = [] u_name = {'Rx': "X", 'Ry': "Y", 'Rxx': "MS"} - instruction.append(u_name[str(gate)[0:int(len(cmd.qubits) + 1)]]) + instruction.append(u_name[str(gate)[0 : int(len(cmd.qubits) + 1)]]) # noqa: E203 instruction.append(round(angle, 2)) instruction.append(qubits) self._circuit.append(instruction) @@ -187,14 +187,16 @@ def _logical_to_physical(self, qb_id): raise RuntimeError( "Unknown qubit id {}. Please make sure " "eng.flush() was called and that the qubit " - "was eliminated during optimization.".format(qb_id)) + "was eliminated during optimization.".format(qb_id) + ) return mapping[qb_id] except AttributeError: if qb_id not in self._mapper: raise RuntimeError( "Unknown qubit id {}. Please make sure " "eng.flush() was called and that the qubit " - "was eliminated during optimization.".format(qb_id)) + "was eliminated during optimization.".format(qb_id) + ) return qb_id def get_probabilities(self, qureg): @@ -229,8 +231,7 @@ def get_probabilities(self, qureg): probability = self._probabilities[state] mapped_state = "".join(mapped_state) - probability_dict[mapped_state] = ( - probability_dict.get(mapped_state, 0) + probability) + probability_dict[mapped_state] = probability_dict.get(mapped_state, 0) + probability return probability_dict def _run(self): @@ -259,28 +260,32 @@ def _run(self): raise Exception("Number of shots limited to 200") try: 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) + res = send( + info, + device=self.device, + token=self._token, + shots=self._num_runs, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) else: - res = retrieve(device=self.device, - token=self._token, - jobid=self._retrieve_execution, - num_retries=self._num_retries, - interval=self._interval, - verbose=self._verbose) + res = retrieve( + device=self.device, + token=self._token, + jobid=self._retrieve_execution, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) self._num_runs = len(res) counts = _format_counts(res, n_qubit) # Determine random outcome P = random.random() - p_sum = 0. + p_sum = 0.0 measured = "" for state in counts: - probability = counts[state] * 1. / self._num_runs + probability = counts[state] * 1.0 / self._num_runs p_sum += probability star = "" if p_sum >= P and measured == "": @@ -290,7 +295,7 @@ def _run(self): if self._verbose and probability > 0: print(str(state) + " with p = " + str(probability) + star) - class QB(): + class QB: def __init__(self, qubit_id): self.id = qubit_id diff --git a/projectq/backends/_aqt/_aqt_http_client.py b/projectq/backends/_aqt/_aqt_http_client.py index b25dc1137..05fa0220a 100644 --- a/projectq/backends/_aqt/_aqt_http_client.py +++ b/projectq/backends/_aqt/_aqt_http_client.py @@ -1,4 +1,5 @@ -# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# -*- coding: utf-8 -*- +# Copyright 2020, 2021 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. @@ -11,7 +12,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. - """ Back-end to run quantum program on AQT cloud platform""" import getpass @@ -45,21 +45,13 @@ def update_devices_list(self, verbose=False): """ # TODO: update once the API for getting online devices is available self.backends = dict() - self.backends['aqt_simulator'] = { - 'nq': 11, - 'version': '0.0.1', - 'url': 'sim/' - } + self.backends['aqt_simulator'] = {'nq': 11, 'version': '0.0.1', 'url': 'sim/'} self.backends['aqt_simulator_noise'] = { 'nq': 11, 'version': '0.0.1', - 'url': 'sim/noise-model-1' - } - self.backends['aqt_device'] = { - 'nq': 4, - 'version': '0.0.1', - 'url': 'lint/' + 'url': 'sim/noise-model-1', } + self.backends['aqt_device'] = {'nq': 4, 'version': '0.0.1', 'url': 'lint/'} if verbose: print('- List of AQT devices available:') print(self.backends) @@ -90,10 +82,7 @@ def _authenticate(self, token=None): """ if token is None: token = getpass.getpass(prompt='AQT token > ') - self.headers.update({ - 'Ocp-Apim-Subscription-Key': token, - 'SDK': 'ProjectQ' - }) + self.headers.update({'Ocp-Apim-Subscription-Key': token, 'SDK': 'ProjectQ'}) self.token = token def _run(self, info, device): @@ -101,11 +90,9 @@ def _run(self, info, device): 'data': info['circuit'], 'access_token': self.token, 'repetitions': info['shots'], - 'no_qubits': info['nq'] + 'no_qubits': info['nq'], } - req = super(AQT, self).put(urljoin(_API_URL, - self.backends[device]['url']), - data=argument) + req = super(AQT, self).put(urljoin(_API_URL, self.backends[device]['url']), data=argument) req.raise_for_status() r_json = req.json() if r_json['status'] != 'queued': @@ -113,12 +100,7 @@ def _run(self, info, device): execution_id = r_json["id"] return execution_id - def _get_result(self, - device, - execution_id, - num_retries=3000, - interval=1, - verbose=False): + def _get_result(self, device, execution_id, num_retries=3000, interval=1, verbose=False): if verbose: print("Waiting for results. [Job ID: {}]".format(execution_id)) @@ -126,9 +108,7 @@ def _get_result(self, original_sigint_handler = signal.getsignal(signal.SIGINT) def _handle_sigint_during_get_result(*_): # pragma: no cover - 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) @@ -136,34 +116,29 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover for retries in range(num_retries): argument = {'id': execution_id, 'access_token': self.token} - req = super(AQT, - self).put(urljoin(_API_URL, - self.backends[device]['url']), - data=argument) + req = super(AQT, self).put(urljoin(_API_URL, self.backends[device]['url']), data=argument) req.raise_for_status() r_json = req.json() if r_json['status'] == 'finished' or 'samples' in r_json: return r_json['samples'] if r_json['status'] != 'running': - raise Exception("Error while running the code: {}.".format( - r_json['status'])) + raise Exception("Error while running the code: {}.".format(r_json['status'])) time.sleep(interval) if self.is_online(device) and retries % 60 == 0: self.update_devices_list() - + # TODO: update once the API for getting online devices is # available if not self.is_online(device): # pragma: no cover raise DeviceOfflineError( - "Device went offline. The ID of " - "your submitted job is {}.".format(execution_id)) + "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): @@ -190,12 +165,7 @@ def show_devices(verbose=False): return aqt_session.backends -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. @@ -210,21 +180,19 @@ def retrieve(device, aqt_session = AQT() aqt_session._authenticate(token) aqt_session.update_devices_list(verbose) - res = aqt_session._get_result(device, - jobid, - num_retries=num_retries, - interval=interval, - verbose=verbose) + res = aqt_session._get_result(device, jobid, num_retries=num_retries, interval=interval, verbose=verbose) return res -def send(info, - device='aqt_simulator', - token=None, - shots=100, - num_retries=100, - interval=1, - verbose=False): +def send( + info, + device='aqt_simulator', + token=None, + shots=100, + num_retries=100, + interval=1, + verbose=False, +): """ Sends cicruit through the AQT API and runs the quantum circuit. @@ -256,8 +224,7 @@ def send(info, online = aqt_session.is_online(device) # useless for the moment if not online: # pragma: no cover - print("The device is offline (for maintenance?). Use the " - "simulator instead or try again later.") + 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 @@ -266,19 +233,21 @@ def send(info, 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)) + "with more qubits".format(qmax, qneeded) + ) raise DeviceTooSmall("Device is too small.") if verbose: print("- Running code: {}".format(info)) execution_id = aqt_session._run(info, device) if verbose: print("- Waiting for results...") - res = aqt_session._get_result(device, - execution_id, - num_retries=num_retries, - interval=interval, - verbose=verbose) + res = aqt_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/_aqt/_aqt_http_client_test.py b/projectq/backends/_aqt/_aqt_http_client_test.py index 97bf9fef1..401c35e5c 100644 --- a/projectq/backends/_aqt/_aqt_http_client_test.py +++ b/projectq/backends/_aqt/_aqt_http_client_test.py @@ -1,4 +1,5 @@ -# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# -*- coding: utf-8 -*- +# Copyright 2020, 2021 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. @@ -49,59 +50,40 @@ def test_show_devices(): def test_send_too_many_qubits(monkeypatch): info = { - 'circuit': - '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' + 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' '["Y", 3.5, [1]], ["X", 3.5, [2]]]', - 'nq': - 100, - 'shots': - 1, - 'backend': { - 'name': 'aqt_simulator' - } + 'nq': 100, + 'shots': 1, + 'backend': {'name': 'aqt_simulator'}, } token = "access" shots = 1 # Code to test: with pytest.raises(_aqt_http_client.DeviceTooSmall): - _aqt_http_client.send(info, - device="aqt_simulator", - token=token, - shots=shots, - verbose=True) + _aqt_http_client.send(info, device="aqt_simulator", token=token, shots=shots, verbose=True) def test_send_real_device_online_verbose(monkeypatch): json_aqt = { - 'data': - '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' + 'data': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' '["Y", 3.5, [1]], ["X", 3.5, [2]]]', - 'access_token': - 'access', - 'repetitions': - 1, - 'no_qubits': - 3 + 'access_token': 'access', + 'repetitions': 1, + 'no_qubits': 3, } info = { - 'circuit': - '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' + 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' '["Y", 3.5, [1]], ["X", 3.5, [2]]]', - 'nq': - 3, - 'shots': - 1, - 'backend': { - 'name': 'aqt_simulator' - } + 'nq': 3, + 'shots': 1, + 'backend': {'name': 'aqt_simulator'}, } token = "access" shots = 1 - device = "aqt_simulator" execution_id = '3' result_ready = [False] result = "my_result" @@ -126,28 +108,27 @@ def raise_for_status(self): pass # Run code - if (args[1] == urljoin(_api_url, "sim/") and kwargs["data"] == json_aqt - and request_num[0] == 0): + if args[1] == urljoin(_api_url, "sim/") and kwargs["data"] == json_aqt and request_num[0] == 0: request_num[0] += 1 - return MockPutResponse({ - "id": execution_id, - "status": "queued" - }, 200) - elif (args[1] == urljoin(_api_url, "sim/") - and kwargs["data"]["access_token"] == token - and kwargs["data"]["id"] == execution_id and not result_ready[0] - and request_num[0] == 1): + return MockPutResponse({"id": execution_id, "status": "queued"}, 200) + elif ( + args[1] == urljoin(_api_url, "sim/") + and kwargs["data"]["access_token"] == token + and kwargs["data"]["id"] == execution_id + and not result_ready[0] + and request_num[0] == 1 + ): result_ready[0] = True request_num[0] += 1 return MockPutResponse({"status": 'running'}, 200) - elif (args[1] == urljoin(_api_url, "sim/") - and kwargs["data"]["access_token"] == token - and kwargs["data"]["id"] == execution_id and result_ready[0] - and request_num[0] == 2): - return MockPutResponse({ - "samples": result, - "status": 'finished' - }, 200) + elif ( + args[1] == urljoin(_api_url, "sim/") + and kwargs["data"]["access_token"] == token + and kwargs["data"]["id"] == execution_id + and result_ready[0] + and request_num[0] == 2 + ): + return MockPutResponse({"samples": result, "status": 'finished'}, 200) monkeypatch.setattr("requests.sessions.Session.put", mocked_requests_put) @@ -158,11 +139,7 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) # Code to test: - res = _aqt_http_client.send(info, - device="aqt_simulator", - token=None, - shots=shots, - verbose=True) + res = _aqt_http_client.send(info, device="aqt_simulator", token=None, shots=shots, verbose=True) assert res == result @@ -182,23 +159,14 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 info = { - 'circuit': - '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' + 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' '["Y", 3.5, [1]], ["X", 3.5, [2]]]', - 'nq': - 3, - 'shots': - 1, - 'backend': { - 'name': 'aqt_simulator' - } + 'nq': 3, + 'shots': 1, + 'backend': {'name': 'aqt_simulator'}, } - _aqt_http_client.send(info, - device="aqt_simulator", - token=None, - shots=shots, - verbose=True) + _aqt_http_client.send(info, device="aqt_simulator", token=None, shots=shots, verbose=True) def test_send_that_errors_are_caught2(monkeypatch): @@ -217,23 +185,14 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 info = { - 'circuit': - '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' + 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' '["Y", 3.5, [1]], ["X", 3.5, [2]]]', - 'nq': - 3, - 'shots': - 1, - 'backend': { - 'name': 'aqt_simulator' - } + 'nq': 3, + 'shots': 1, + 'backend': {'name': 'aqt_simulator'}, } - _aqt_http_client.send(info, - device="aqt_simulator", - token=None, - shots=shots, - verbose=True) + _aqt_http_client.send(info, device="aqt_simulator", token=None, shots=shots, verbose=True) def test_send_that_errors_are_caught3(monkeypatch): @@ -252,23 +211,14 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 info = { - 'circuit': - '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' + 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' '["Y", 3.5, [1]], ["X", 3.5, [2]]]', - 'nq': - 3, - 'shots': - 1, - 'backend': { - 'name': 'aqt_simulator' - } + 'nq': 3, + 'shots': 1, + 'backend': {'name': 'aqt_simulator'}, } - _aqt_http_client.send(info, - device="aqt_simulator", - token=None, - shots=shots, - verbose=True) + _aqt_http_client.send(info, device="aqt_simulator", token=None, shots=shots, verbose=True) def test_send_that_errors_are_caught4(monkeypatch): @@ -276,19 +226,11 @@ def test_send_that_errors_are_caught4(monkeypatch): 'data': '[]', 'access_token': 'access', 'repetitions': 1, - 'no_qubits': 3 - } - info = { - 'circuit': '[]', - 'nq': 3, - 'shots': 1, - 'backend': { - 'name': 'aqt_simulator' - } + 'no_qubits': 3, } + info = {'circuit': '[]', 'nq': 3, 'shots': 1, 'backend': {'name': 'aqt_simulator'}} token = "access" shots = 1 - device = "aqt_simulator" execution_id = '123e' def mocked_requests_put(*args, **kwargs): @@ -310,55 +252,43 @@ def raise_for_status(self): pass # Run code - if (args[1] == urljoin(_api_url, "sim/") - and kwargs["data"] == json_aqt): - return MockPutResponse({ - "id": execution_id, - "status": "error" - }, 200) + if args[1] == urljoin(_api_url, "sim/") and kwargs["data"] == json_aqt: + return MockPutResponse({"id": execution_id, "status": "error"}, 200) monkeypatch.setattr("requests.sessions.Session.put", mocked_requests_put) # Code to test: _aqt_http_client.time.sleep = lambda x: x with pytest.raises(Exception): - _aqt_http_client.send(info, - device="aqt_simulator", - token=token, - num_retries=10, - shots=shots, - verbose=True) + _aqt_http_client.send( + info, + device="aqt_simulator", + token=token, + num_retries=10, + shots=shots, + verbose=True, + ) def test_timeout_exception(monkeypatch): json_aqt = { - 'data': - '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' + 'data': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' '["Y", 3.5, [1]], ["X", 3.5, [2]]]', - 'access_token': - 'access', - 'repetitions': - 1, - 'no_qubits': - 3 + 'access_token': 'access', + 'repetitions': 1, + 'no_qubits': 3, } info = { - 'circuit': - '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' + 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' '["Y", 3.5, [1]], ["X", 3.5, [2]]]', - 'nq': - 3, - 'shots': - 1, - 'backend': { - 'name': 'aqt_simulator' - } + 'nq': 3, + 'shots': 1, + 'backend': {'name': 'aqt_simulator'}, } token = "access" shots = 1 - device = "aqt_simulator" execution_id = '123e' tries = [0] @@ -381,15 +311,13 @@ def raise_for_status(self): pass # Run code - if (args[1] == urljoin(_api_url, "sim/") - and kwargs["data"] == json_aqt): - return MockPutResponse({ - "id": execution_id, - "status": "queued" - }, 200) - if (args[1] == urljoin(_api_url, "sim/") - and kwargs["data"]["access_token"] == token - and kwargs["data"]["id"] == execution_id): + if args[1] == urljoin(_api_url, "sim/") and kwargs["data"] == json_aqt: + return MockPutResponse({"id": execution_id, "status": "queued"}, 200) + if ( + args[1] == urljoin(_api_url, "sim/") + and kwargs["data"]["access_token"] == token + and kwargs["data"]["id"] == execution_id + ): tries[0] += 1 return MockPutResponse({"status": 'running'}, 200) @@ -405,19 +333,20 @@ def user_password_input(prompt): _aqt_http_client.time.sleep = lambda x: x for tok in (None, token): with pytest.raises(Exception) as excinfo: - _aqt_http_client.send(info, - device="aqt_simulator", - token=tok, - num_retries=10, - shots=shots, - verbose=True) + _aqt_http_client.send( + info, + device="aqt_simulator", + token=tok, + num_retries=10, + shots=shots, + verbose=True, + ) assert "123e" in str(excinfo.value) # check that job id is in exception assert tries[0] > 0 def test_retrieve(monkeypatch): token = "access" - device = "aqt_simulator" execution_id = '123e' result_ready = [False] result = "my_result" @@ -442,21 +371,24 @@ def raise_for_status(self): pass # Run code - if (args[1] == urljoin(_api_url, "sim/") - and kwargs["data"]["access_token"] == token - and kwargs["data"]["id"] == execution_id - and not result_ready[0] and request_num[0] < 1): + if ( + args[1] == urljoin(_api_url, "sim/") + and kwargs["data"]["access_token"] == token + and kwargs["data"]["id"] == execution_id + and not result_ready[0] + and request_num[0] < 1 + ): result_ready[0] = True request_num[0] += 1 return MockPutResponse({"status": 'running'}, 200) - if (args[1] == urljoin(_api_url, "sim/") - and kwargs["data"]["access_token"] == token - and kwargs["data"]["id"] == execution_id and result_ready[0] - and request_num[0] == 1): - return MockPutResponse({ - "samples": result, - "status": 'finished' - }, 200) + if ( + args[1] == urljoin(_api_url, "sim/") + and kwargs["data"]["access_token"] == token + and kwargs["data"]["id"] == execution_id + and result_ready[0] + and request_num[0] == 1 + ): + return MockPutResponse({"samples": result, "status": 'finished'}, 200) monkeypatch.setattr("requests.sessions.Session.put", mocked_requests_put) @@ -468,16 +400,12 @@ def user_password_input(prompt): # Code to test: _aqt_http_client.time.sleep = lambda x: x - res = _aqt_http_client.retrieve(device="aqt_simulator", - token=None, - verbose=True, - jobid="123e") + res = _aqt_http_client.retrieve(device="aqt_simulator", token=None, verbose=True, jobid="123e") assert res == result def test_retrieve_that_errors_are_caught(monkeypatch): token = "access" - device = "aqt_simulator" execution_id = '123e' result_ready = [False] request_num = [0] # To assert correct order of calls @@ -501,17 +429,23 @@ def raise_for_status(self): pass # Run code - if (args[1] == urljoin(_api_url, "sim/") - and kwargs["data"]["access_token"] == token - and kwargs["data"]["id"] == execution_id - and not result_ready[0] and request_num[0] < 1): + if ( + args[1] == urljoin(_api_url, "sim/") + and kwargs["data"]["access_token"] == token + and kwargs["data"]["id"] == execution_id + and not result_ready[0] + and request_num[0] < 1 + ): result_ready[0] = True request_num[0] += 1 return MockPutResponse({"status": 'running'}, 200) - if (args[1] == urljoin(_api_url, "sim/") - and kwargs["data"]["access_token"] == token - and kwargs["data"]["id"] == execution_id and result_ready[0] - and request_num[0] == 1): + if ( + args[1] == urljoin(_api_url, "sim/") + and kwargs["data"]["access_token"] == token + and kwargs["data"]["id"] == execution_id + and result_ready[0] + and request_num[0] == 1 + ): return MockPutResponse({"status": 'error'}, 200) monkeypatch.setattr("requests.sessions.Session.put", mocked_requests_put) @@ -519,7 +453,4 @@ def raise_for_status(self): # Code to test: _aqt_http_client.time.sleep = lambda x: x with pytest.raises(Exception): - _aqt_http_client.retrieve(device="aqt_simulator", - token=token, - verbose=True, - jobid="123e") + _aqt_http_client.retrieve(device="aqt_simulator", token=token, verbose=True, jobid="123e") diff --git a/projectq/backends/_aqt/_aqt_test.py b/projectq/backends/_aqt/_aqt_test.py index 7006f5c6a..0810cfe0a 100644 --- a/projectq/backends/_aqt/_aqt_test.py +++ b/projectq/backends/_aqt/_aqt_test.py @@ -1,4 +1,5 @@ -# Copyright 2020 ProjectQ-Framework (www.projectq.ch) +# -*- coding: utf-8 -*- +# Copyright 2020, 2021 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. @@ -20,9 +21,27 @@ from projectq.backends._aqt import _aqt from projectq.types import WeakQubitRef, Qubit from projectq.cengines import DummyEngine, BasicMapperEngine -from projectq.ops import (All, Allocate, Barrier, Command, Deallocate, - Entangle, Measure, NOT, Rx, Ry, Rz, Rxx, S, Sdag, T, - Tdag, X, Y, Z) +from projectq.ops import ( + All, + Allocate, + Barrier, + Command, + Deallocate, + Entangle, + Measure, + NOT, + Rx, + Ry, + Rz, + Rxx, + S, + Sdag, + T, + Tdag, + X, + Y, + Z, +) # Insure that no HTTP request can be made in all tests in this module @@ -31,33 +50,45 @@ def no_requests(monkeypatch): monkeypatch.delattr("requests.sessions.Session.request") -@pytest.mark.parametrize("single_qubit_gate, is_available", - [(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), False), (Rxx(0.5), True), - (Barrier, True), (Entangle, False)]) +@pytest.mark.parametrize( + "single_qubit_gate, is_available", + [ + (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), False), + (Rxx(0.5), True), + (Barrier, True), + (Entangle, False), + ], +) def test_aqt_backend_is_available(single_qubit_gate, is_available): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() aqt_backend = _aqt.AQTBackend() - cmd = Command(eng, single_qubit_gate, (qubit1, )) + cmd = Command(eng, single_qubit_gate, (qubit1,)) assert aqt_backend.is_available(cmd) == is_available -@pytest.mark.parametrize("num_ctrl_qubits, is_available", [(0, True), - (1, False), - (2, False), - (3, False)]) +@pytest.mark.parametrize("num_ctrl_qubits, is_available", [(0, True), (1, False), (2, False), (3, False)]) def test_aqt_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) aqt_backend = _aqt.AQTBackend() - cmd = Command(eng, Rx(0.5), (qubit1, ), controls=qureg) + cmd = Command(eng, Rx(0.5), (qubit1,), controls=qureg) assert aqt_backend.is_available(cmd) == is_available - cmd = Command(eng, Rxx(0.5), (qubit1, ), controls=qureg) + cmd = Command(eng, Rxx(0.5), (qubit1,), controls=qureg) assert aqt_backend.is_available(cmd) == is_available @@ -76,7 +107,7 @@ def test_aqt_invalid_command(): backend = _aqt.AQTBackend(verbose=True) qb = WeakQubitRef(None, 1) - cmd = Command(None, gate=S, qubits=[(qb, )]) + cmd = Command(None, gate=S, qubits=[(qb,)]) with pytest.raises(Exception): backend.receive([cmd]) @@ -116,9 +147,7 @@ def mock_retrieve(*args, **kwargs): return [0, 6, 0, 6, 0, 0, 0, 6, 0, 6] monkeypatch.setattr(_aqt, "retrieve", mock_retrieve) - backend = _aqt.AQTBackend( - retrieve_execution="a3877d18-314f-46c9-86e7-316bc4dbe968", - verbose=True) + backend = _aqt.AQTBackend(retrieve_execution="a3877d18-314f-46c9-86e7-316bc4dbe968", verbose=True) eng = MainEngine(backend=backend, engine_list=[]) unused_qubit = eng.allocate_qubit() @@ -149,17 +178,12 @@ def mock_retrieve(*args, **kwargs): def test_aqt_backend_functional_test(monkeypatch): correct_info = { - 'circuit': - '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' + 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' '["Y", 3.5, [1]], ["X", 3.5, [2]]]', - 'nq': - 3, - 'shots': - 10, - 'backend': { - 'name': 'simulator' - } + 'nq': 3, + 'shots': 10, + 'backend': {'name': 'simulator'}, } def mock_send(*args, **kwargs): diff --git a/projectq/backends/_awsbraket/__init__.py b/projectq/backends/_awsbraket/__init__.py index 8641d77b9..db8d7a6ff 100644 --- a/projectq/backends/_awsbraket/__init__.py +++ b/projectq/backends/_awsbraket/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,10 +17,13 @@ from ._awsbraket import AWSBraketBackend except ImportError: # pragma: no cover import warnings - warnings.warn("Failed to import one of the dependencies required to use " - "the Amazon Braket Backend.\n" - "Did you install ProjectQ using the [braket] extra? " - "(python3 -m pip install projectq[braket])") + + warnings.warn( + "Failed to import one of the dependencies required to use " + "the Amazon Braket Backend.\n" + "Did you install ProjectQ using the [braket] extra? " + "(python3 -m pip install projectq[braket])" + ) # Make sure that the symbol is defined class AWSBraketBackend: diff --git a/projectq/backends/_awsbraket/_awsbraket.py b/projectq/backends/_awsbraket/_awsbraket.py index fbb38f034..2eaf7ba00 100755 --- a/projectq/backends/_awsbraket/_awsbraket.py +++ b/projectq/backends/_awsbraket/_awsbraket.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,10 +20,29 @@ from projectq.cengines import BasicEngine from projectq.meta import get_control_count, LogicalQubitIDTag from projectq.types import WeakQubitRef -from projectq.ops import (R, SwapGate, HGate, Rx, Ry, Rz, SGate, Sdag, TGate, - Tdag, XGate, YGate, ZGate, SqrtXGate, Measure, - Allocate, Deallocate, Barrier, FlushGate, - DaggeredGate) +from projectq.ops import ( + R, + SwapGate, + HGate, + Rx, + Ry, + Rz, + SGate, + Sdag, + TGate, + Tdag, + XGate, + YGate, + ZGate, + SqrtXGate, + Measure, + Allocate, + Deallocate, + Barrier, + FlushGate, + DaggeredGate, +) + # TODO: Add MatrixGate to cover the unitary operation in the SV1 simulator from ._awsbraket_boto3_client import send, retrieve @@ -34,16 +54,19 @@ class AWSBraketBackend(BasicEngine): transforms it to Braket compatible, and sends the circuit through the Boto3 and Amazon Braket SDK. """ - def __init__(self, - use_hardware=False, - num_runs=1000, - verbose=False, - credentials=None, - s3_folder=None, - device='Aspen-8', - num_retries=30, - interval=1, - retrieve_execution=None): + + def __init__( + self, + use_hardware=False, + num_runs=1000, + verbose=False, + credentials=None, + s3_folder=None, + device='Aspen-8', + num_retries=30, + interval=1, + retrieve_execution=None, + ): """ Initialize the Backend object. @@ -101,7 +124,7 @@ def __init__(self, SGate: 's', # NB: Sdag is 'si' TGate: 't', # NB: Tdag is 'ti' SwapGate: 'swap', - SqrtXGate: 'v' + SqrtXGate: 'v', } # Static head and tail to be added to the circuit @@ -159,17 +182,49 @@ def is_available(self, cmd): if get_control_count(cmd) == 1: return isinstance(gate, (R, ZGate, XGate, SwapGate)) if get_control_count(cmd) == 0: - return isinstance( - gate, (R, Rx, Ry, Rz, XGate, YGate, ZGate, HGate, SGate, - TGate, SwapGate)) or gate in (Sdag, Tdag) + return ( + isinstance( + gate, + ( + R, + Rx, + Ry, + Rz, + XGate, + YGate, + ZGate, + HGate, + SGate, + TGate, + SwapGate, + ), + ) + or gate in (Sdag, Tdag) + ) if self.device == 'IonQ Device': if get_control_count(cmd) == 1: return isinstance(gate, XGate) if get_control_count(cmd) == 0: - return isinstance( - gate, (Rx, Ry, Rz, XGate, YGate, ZGate, HGate, SGate, - TGate, SqrtXGate, SwapGate)) or gate in (Sdag, Tdag) + return ( + isinstance( + gate, + ( + Rx, + Ry, + Rz, + XGate, + YGate, + ZGate, + HGate, + SGate, + TGate, + SqrtXGate, + SwapGate, + ), + ) + or gate in (Sdag, Tdag) + ) if self.device == 'SV1': if get_control_count(cmd) == 2: @@ -179,13 +234,30 @@ def is_available(self, cmd): if get_control_count(cmd) == 0: # TODO: add MatrixGate to cover the unitary operation # TODO: Missing XY gate in ProjectQ - return isinstance( - gate, (R, Rx, Ry, Rz, XGate, YGate, ZGate, HGate, SGate, - TGate, SqrtXGate, SwapGate)) or gate in (Sdag, Tdag) + return ( + isinstance( + gate, + ( + R, + Rx, + Ry, + Rz, + XGate, + YGate, + ZGate, + HGate, + SGate, + TGate, + SqrtXGate, + SwapGate, + ), + ) + or gate in (Sdag, Tdag) + ) return False def _reset(self): - """ Reset all temporary variables (after flush gate). """ + """Reset all temporary variables (after flush gate).""" self._clear = True self._measured_ids = [] @@ -207,8 +279,7 @@ def _store(self, cmd): gate = cmd.gate num_controls = get_control_count(cmd) - gate_type = (type(gate) if not isinstance(gate, DaggeredGate) else - type(gate._gate)) + gate_type = type(gate) if not isinstance(gate, DaggeredGate) else type(gate._gate) if gate == Allocate: self._allocated_qubits.add(cmd.qubits[0][0].id) @@ -223,8 +294,7 @@ def _store(self, cmd): if isinstance(tag, LogicalQubitIDTag): logical_id = tag.logical_qubit_id break - self._measured_ids.append( - logical_id if logical_id is not None else qb_id) + self._measured_ids.append(logical_id if logical_id is not None else qb_id) return # All other supported gate types @@ -245,8 +315,7 @@ def _store(self, cmd): json_cmd['angle'] = gate.angle if isinstance(gate, DaggeredGate): - json_cmd['type'] = ('c' * num_controls + self._gationary[gate_type] - + 'i') + json_cmd['type'] = 'c' * num_controls + self._gationary[gate_type] + 'i' elif isinstance(gate, (XGate)) and num_controls > 0: json_cmd['type'] = 'c' * (num_controls - 1) + 'cnot' else: @@ -270,7 +339,8 @@ def _logical_to_physical(self, qb_id): raise RuntimeError( "Unknown qubit id {} in current mapping. Please make sure " "eng.flush() was called and that the qubit " - "was eliminated during optimization.".format(qb_id)) + "was eliminated during optimization.".format(qb_id) + ) return mapping[qb_id] return qb_id @@ -346,11 +416,13 @@ def _run(self): # You can recover the results from previous jobs using the TaskArn # (self._retrieve_execution). if self._retrieve_execution is not None: - res = retrieve(credentials=self._credentials, - taskArn=self._retrieve_execution, - num_retries=self._num_retries, - interval=self._interval, - verbose=self._verbose) + res = retrieve( + credentials=self._credentials, + taskArn=self._retrieve_execution, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) else: # Return if no operations have been added. if not self._circuit: @@ -358,25 +430,25 @@ def _run(self): n_qubit = len(self._allocated_qubits) info = {} - info['circuit'] = self._circuithead + \ - self._circuit.rstrip(', ') + \ - self._circuittail + info['circuit'] = self._circuithead + self._circuit.rstrip(', ') + self._circuittail info['nq'] = n_qubit info['shots'] = self._num_runs info['backend'] = {'name': self.device} - res = send(info, - device=self.device, - credentials=self._credentials, - s3_folder=self._s3_folder, - num_retries=self._num_retries, - interval=self._interval, - verbose=self._verbose) + res = send( + info, + device=self.device, + credentials=self._credentials, + s3_folder=self._s3_folder, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) counts = res # Determine random outcome P = random.random() - p_sum = 0. + p_sum = 0.0 measured = "" for state in counts: probability = counts[state] @@ -392,8 +464,7 @@ def _run(self): # register measurement result for qubit_id in self._measured_ids: result = int(measured[self._logical_to_physical(qubit_id)]) - self.main_engine.set_measurement_result( - WeakQubitRef(self.main_engine, qubit_id), result) + self.main_engine.set_measurement_result(WeakQubitRef(self.main_engine, qubit_id), result) self._reset() def receive(self, command_list): diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py index 0e74b3013..533557653 100755 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,10 +30,11 @@ import json -class AWSBraket(): +class AWSBraket: """ Manage a session between ProjectQ and AWS Braket service. """ + def __init__(self): self.backends = dict() self.timeout = 5.0 @@ -46,10 +48,8 @@ def _authenticate(self, credentials=None): AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. """ if credentials is None: # pragma: no cover - credentials['AWS_ACCESS_KEY_ID'] = getpass.getpass( - prompt="Enter AWS_ACCESS_KEY_ID: ") - credentials['AWS_SECRET_KEY'] = getpass.getpass( - prompt="Enter AWS_SECRET_KEY: ") + credentials['AWS_ACCESS_KEY_ID'] = getpass.getpass(prompt="Enter AWS_ACCESS_KEY_ID: ") + credentials['AWS_SECRET_KEY'] = getpass.getpass(prompt="Enter AWS_SECRET_KEY: ") self._credentials = credentials @@ -61,8 +61,7 @@ def _get_s3_folder(self, s3_folder=None): """ if s3_folder is None: # pragma: no cover S3Bucket = input("Enter the S3 Bucket configured in Braket: ") - S3Directory = input( - "Enter the Directory created in the S3 Bucket: ") + S3Directory = input("Enter the Directory created in the S3 Bucket: ") s3_folder = [S3Bucket, S3Directory] self._s3_folder = s3_folder @@ -89,7 +88,8 @@ def get_list_devices(self, verbose=False): 'braket', region_name=region, aws_access_key_id=self._credentials['AWS_ACCESS_KEY_ID'], - aws_secret_access_key=self._credentials['AWS_SECRET_KEY']) + aws_secret_access_key=self._credentials['AWS_SECRET_KEY'], + ) filters = [] devicelist = client.search_devices(filters=filters) for result in devicelist['devices']: @@ -97,51 +97,39 @@ def get_list_devices(self, verbose=False): continue if result['deviceType'] == 'QPU': deviceCapabilities = json.loads( - client.get_device(deviceArn=result['deviceArn']) - ['deviceCapabilities']) + client.get_device(deviceArn=result['deviceArn'])['deviceCapabilities'] + ) self.backends[result['deviceName']] = { - 'nq': - deviceCapabilities['paradigm']['qubitCount'], - 'coupling_map': - deviceCapabilities['paradigm']['connectivity'] - ['connectivityGraph'], - 'version': - deviceCapabilities['braketSchemaHeader']['version'], - 'location': - region, # deviceCapabilities['service']['deviceLocation'], - 'deviceArn': - result['deviceArn'], - 'deviceParameters': - deviceCapabilities['deviceParameters']['properties'] - ['braketSchemaHeader']['const'], - 'deviceModelParameters': - deviceCapabilities['deviceParameters']['definitions'] - ['GateModelParameters']['properties'] - ['braketSchemaHeader']['const'], + 'nq': deviceCapabilities['paradigm']['qubitCount'], + 'coupling_map': deviceCapabilities['paradigm']['connectivity']['connectivityGraph'], + 'version': deviceCapabilities['braketSchemaHeader']['version'], + 'location': region, # deviceCapabilities['service']['deviceLocation'], + 'deviceArn': result['deviceArn'], + 'deviceParameters': deviceCapabilities['deviceParameters']['properties']['braketSchemaHeader'][ + 'const' + ], + 'deviceModelParameters': deviceCapabilities['deviceParameters']['definitions'][ + 'GateModelParameters' + ]['properties']['braketSchemaHeader']['const'], } # Unfortunatelly the Capabilities schemas are not homogeneus # for real devices and simulators elif result['deviceType'] == 'SIMULATOR': deviceCapabilities = json.loads( - client.get_device(deviceArn=result['deviceArn']) - ['deviceCapabilities']) + client.get_device(deviceArn=result['deviceArn'])['deviceCapabilities'] + ) self.backends[result['deviceName']] = { - 'nq': - deviceCapabilities['paradigm']['qubitCount'], + 'nq': deviceCapabilities['paradigm']['qubitCount'], 'coupling_map': {}, - 'version': - deviceCapabilities['braketSchemaHeader']['version'], - 'location': - 'us-east-1', - 'deviceArn': - result['deviceArn'], - 'deviceParameters': - deviceCapabilities['deviceParameters']['properties'] - ['braketSchemaHeader']['const'], - 'deviceModelParameters': - deviceCapabilities['deviceParameters']['definitions'] - ['GateModelParameters']['properties'] - ['braketSchemaHeader']['const'], + 'version': deviceCapabilities['braketSchemaHeader']['version'], + 'location': 'us-east-1', + 'deviceArn': result['deviceArn'], + 'deviceParameters': deviceCapabilities['deviceParameters']['properties']['braketSchemaHeader'][ + 'const' + ], + 'deviceModelParameters': deviceCapabilities['deviceParameters']['definitions'][ + 'GateModelParameters' + ]['properties']['braketSchemaHeader']['const'], } if verbose: @@ -206,12 +194,9 @@ def _run(self, info, device): device_parameters = { 'braketSchemaHeader': self.backends[device]['deviceParameters'], 'paradigmParameters': { - 'braketSchemaHeader': - self.backends[device]['deviceModelParameters'], - 'qubitCount': - info['nq'], - 'disableQubitRewiring': - False, + 'braketSchemaHeader': self.backends[device]['deviceModelParameters'], + 'qubitCount': info['nq'], + 'disableQubitRewiring': False, }, } device_parameters = json.dumps(device_parameters) @@ -220,7 +205,8 @@ def _run(self, info, device): 'braket', region_name=region_name, aws_access_key_id=self._credentials['AWS_ACCESS_KEY_ID'], - aws_secret_access_key=self._credentials['AWS_SECRET_KEY']) + aws_secret_access_key=self._credentials['AWS_SECRET_KEY'], + ) response = client_braket.create_quantum_task( action=argument['circ'], @@ -228,15 +214,12 @@ def _run(self, info, device): deviceParameters=device_parameters, outputS3Bucket=argument['s3_folder'][0], outputS3KeyPrefix=argument['s3_folder'][1], - shots=argument['shots']) + shots=argument['shots'], + ) return response['quantumTaskArn'] - def _get_result(self, - execution_id, - num_retries=30, - interval=1, - verbose=False): + def _get_result(self, execution_id, num_retries=30, interval=1, verbose=False): if verbose: print("Waiting for results. [Job Arn: {}]".format(execution_id)) @@ -244,9 +227,7 @@ def _get_result(self, original_sigint_handler = signal.getsignal(signal.SIGINT) def _handle_sigint_during_get_result(*_): # pragma: no cover - raise Exception( - "Interrupted. The Arn of your submitted job is {}.".format( - execution_id)) + raise Exception("Interrupted. The Arn of your submitted job is {}.".format(execution_id)) def _calculate_measurement_probs(measurements): """ @@ -280,14 +261,14 @@ def _calculate_measurement_probs(measurements): 'braket', region_name=region_name, aws_access_key_id=self._credentials['AWS_ACCESS_KEY_ID'], - aws_secret_access_key=self._credentials['AWS_SECRET_KEY']) + aws_secret_access_key=self._credentials['AWS_SECRET_KEY'], + ) try: signal.signal(signal.SIGINT, _handle_sigint_during_get_result) for _ in range(num_retries): - quantum_task = client_braket.get_quantum_task( - quantumTaskArn=execution_id) + quantum_task = client_braket.get_quantum_task(quantumTaskArn=execution_id) status = quantum_task['status'] bucket = quantum_task['outputS3Bucket'] directory = quantum_task['outputS3Directory'] @@ -295,16 +276,14 @@ def _calculate_measurement_probs(measurements): if status == 'COMPLETED': # Get the device type to obtian the correct measurement # structure - devicetype_used = client_braket.get_device( - deviceArn=quantum_task['deviceArn'])['deviceType'] + devicetype_used = client_braket.get_device(deviceArn=quantum_task['deviceArn'])['deviceType'] # Get the results from S3 - client_s3 = boto3.client('s3', - aws_access_key_id=self. - _credentials['AWS_ACCESS_KEY_ID'], - aws_secret_access_key=self. - _credentials['AWS_SECRET_KEY']) - s3result = client_s3.get_object(Bucket=bucket, - Key=resultsojectname) + client_s3 = boto3.client( + 's3', + aws_access_key_id=self._credentials['AWS_ACCESS_KEY_ID'], + aws_secret_access_key=self._credentials['AWS_SECRET_KEY'], + ) + s3result = client_s3.get_object(Bucket=bucket, Key=resultsojectname) if verbose: print("Results obtained. [Status: {}]".format(status)) result_content = json.loads(s3result['Body'].read()) @@ -312,15 +291,14 @@ def _calculate_measurement_probs(measurements): if devicetype_used == 'QPU': return result_content['measurementProbabilities'] if devicetype_used == 'SIMULATOR': - return _calculate_measurement_probs( - result_content['measurements']) + return _calculate_measurement_probs(result_content['measurements']) if status == 'FAILED': - raise Exception("Error while running the code: {}. " - "The failure reason was: {}.".format( - status, quantum_task['failureReason'])) + raise Exception( + "Error while running the code: {}. " + "The failure reason was: {}.".format(status, quantum_task['failureReason']) + ) if status == 'CANCELLING': - raise Exception("The job received a CANCEL " - "operation: {}.".format(status)) + raise Exception("The job received a CANCEL operation: {}.".format(status)) time.sleep(interval) # NOTE: Be aware that AWS is billing if a lot of API calls are # executed, therefore the num_repetitions is set to a small @@ -335,9 +313,11 @@ def _calculate_measurement_probs(measurements): if original_sigint_handler is not None: signal.signal(signal.SIGINT, original_sigint_handler) - raise Exception("Timeout. " - "The Arn of your submitted job is {} and the status " - "of the job is {}.".format(execution_id, status)) + raise Exception( + "Timeout. " + "The Arn of your submitted job is {} and the status " + "of the job is {}.".format(execution_id, status) + ) class DeviceTooSmall(Exception): @@ -387,13 +367,9 @@ def retrieve(credentials, taskArn, num_retries=30, interval=1, verbose=False): if verbose: print("- Authenticating...") if credentials is not None: - print("AWS credentials: " + credentials['AWS_ACCESS_KEY_ID'] - + ", " + credentials['AWS_SECRET_KEY']) + print("AWS credentials: " + credentials['AWS_ACCESS_KEY_ID'] + ", " + credentials['AWS_SECRET_KEY']) awsbraket_session._authenticate(credentials=credentials) - res = awsbraket_session._get_result(taskArn, - num_retries=num_retries, - interval=interval, - verbose=verbose) + res = awsbraket_session._get_result(taskArn, num_retries=num_retries, interval=interval, verbose=verbose) return res except botocore.exceptions.ClientError as error: error_code = error.response['Error']['Code'] @@ -403,13 +379,7 @@ def retrieve(credentials, taskArn, num_retries=30, interval=1, verbose=False): raise -def send(info, - device, - credentials, - s3_folder, - num_retries=30, - interval=1, - verbose=False): +def send(info, device, credentials, s3_folder, num_retries=30, interval=1, verbose=False): """ Sends cicruit through the Boto3 SDK and runs the quantum circuit. @@ -433,8 +403,7 @@ def send(info, if verbose: print("- Authenticating...") if credentials is not None: - print("AWS credentials: " + credentials['AWS_ACCESS_KEY_ID'] - + ", " + credentials['AWS_SECRET_KEY']) + print("AWS credentials: " + credentials['AWS_ACCESS_KEY_ID'] + ", " + credentials['AWS_SECRET_KEY']) awsbraket_session._authenticate(credentials=credentials) awsbraket_session._get_s3_folder(s3_folder=s3_folder) @@ -442,34 +411,30 @@ def send(info, awsbraket_session.get_list_devices(verbose) online = awsbraket_session.is_online(device) if online: - print("The job will be queued in any case, " - "plase take this into account") + print("The job will be queued in any case, plase take this into account") else: - print("The device is not available. Use the " - "simulator instead or try another device.") + print("The device is not available. Use the simulator instead or try another device.") raise DeviceOfflineError("Device is not available.") # check if the device has enough qubit to run the code - runnable, qmax, qneeded = \ - awsbraket_session.can_run_experiment(info, device) + runnable, qmax, qneeded = awsbraket_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)) + ( + "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)) taskArn = awsbraket_session._run(info, device) - print("Your task Arn is: {}. Make note of that for future reference". - format(taskArn)) + print("Your task Arn is: {}. Make note of that for future reference".format(taskArn)) if verbose: print("- Waiting for results...") - res = awsbraket_session._get_result(taskArn, - num_retries=num_retries, - interval=interval, - verbose=verbose) + res = awsbraket_session._get_result(taskArn, num_retries=num_retries, interval=interval, verbose=verbose) if verbose: print("- Done.") return res diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py index ba0d4062c..4c669d165 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,26 +15,20 @@ """ Test for projectq.backends._awsbraket._awsbraket_boto3_client.py """ import pytest -from unittest.mock import MagicMock, Mock, patch +from unittest.mock import patch -from io import StringIO - -import json - -from ._awsbraket_boto3_client_test_fixtures import * +from ._awsbraket_boto3_client_test_fixtures import * # noqa: F401,F403 # ============================================================================== _has_boto3 = True try: - from botocore.response import StreamingBody import botocore from projectq.backends._awsbraket import _awsbraket_boto3_client except ImportError: _has_boto3 = False -has_boto3 = pytest.mark.skipif(not _has_boto3, - reason="boto3 package is not installed") +has_boto3 = pytest.mark.skipif(not _has_boto3, reason="boto3 package is not installed") # ============================================================================== @@ -62,9 +57,7 @@ def test_show_devices(mock_boto3_client, show_devices_setup): 'quantumTaskArn': 'arntask', 'shots': 123, 'status': 'COMPLETED', - 'tags': { - 'tagkey': 'tagvalue' - } + 'tags': {'tagkey': 'tagvalue'}, } failed_value = { @@ -93,11 +86,15 @@ def test_show_devices(mock_boto3_client, show_devices_setup): @has_boto3 @patch('boto3.client') -@pytest.mark.parametrize("var_status, var_result", - [('completed', completed_value), - ('failed', failed_value), - ('cancelling', cancelling_value), - ('other', other_value)]) +@pytest.mark.parametrize( + "var_status, var_result", + [ + ('completed', completed_value), + ('failed', failed_value), + ('cancelling', cancelling_value), + ('other', other_value), + ], +) def test_retrieve(mock_boto3_client, var_status, var_result, retrieve_setup): arntask, creds, device_value, res_completed, results_dict = retrieve_setup @@ -107,28 +104,28 @@ def test_retrieve(mock_boto3_client, var_status, var_result, retrieve_setup): mock_boto3_client.get_object.return_value = results_dict if var_status == 'completed': - res = _awsbraket_boto3_client.retrieve(credentials=creds, - taskArn=arntask) + res = _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask) assert res == res_completed else: with pytest.raises(Exception) as exinfo: - _awsbraket_boto3_client.retrieve(credentials=creds, - taskArn=arntask, - num_retries=2) + _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask, num_retries=2) print(exinfo.value) if var_status == 'failed': - assert str(exinfo.value) == \ - "Error while running the code: FAILED. \ + assert ( + str(exinfo.value) + == "Error while running the code: FAILED. \ The failure reason was: This is a failure reason." + ) if var_status == 'cancelling': - assert str(exinfo.value) == \ - "The job received a CANCEL operation: CANCELLING." + assert str(exinfo.value) == "The job received a CANCEL operation: CANCELLING." if var_status == 'other': - assert str(exinfo.value) == \ - "Timeout. The Arn of your submitted job \ + assert ( + str(exinfo.value) + == "Timeout. The Arn of your submitted job \ is arn:aws:braket:us-east-1:id:taskuuid \ and the status of the job is OTHER." + ) # ============================================================================== @@ -137,8 +134,13 @@ def test_retrieve(mock_boto3_client, var_status, var_result, retrieve_setup): @has_boto3 @patch('boto3.client') def test_retrieve_devicetypes(mock_boto3_client, retrieve_devicetypes_setup): - (arntask, creds, device_value, results_dict, - res_completed) = retrieve_devicetypes_setup + ( + arntask, + creds, + device_value, + results_dict, + res_completed, + ) = retrieve_devicetypes_setup mock_boto3_client.return_value = mock_boto3_client mock_boto3_client.get_quantum_task.return_value = completed_value @@ -155,18 +157,14 @@ def test_retrieve_devicetypes(mock_boto3_client, retrieve_devicetypes_setup): @has_boto3 @patch('boto3.client') def test_send_too_many_qubits(mock_boto3_client, send_too_many_setup): - (creds, s3_folder, search_value, device_value, - info_too_much) = send_too_many_setup + (creds, s3_folder, search_value, device_value, info_too_much) = send_too_many_setup mock_boto3_client.return_value = mock_boto3_client mock_boto3_client.search_devices.return_value = search_value mock_boto3_client.get_device.return_value = device_value with pytest.raises(_awsbraket_boto3_client.DeviceTooSmall): - _awsbraket_boto3_client.send(info_too_much, - device='name2', - credentials=creds, - s3_folder=s3_folder) + _awsbraket_boto3_client.send(info_too_much, device='name2', credentials=creds, s3_folder=s3_folder) # ============================================================================== @@ -174,16 +172,27 @@ def test_send_too_many_qubits(mock_boto3_client, send_too_many_setup): @has_boto3 @patch('boto3.client') -@pytest.mark.parametrize("var_status, var_result", - [('completed', completed_value), - ('failed', failed_value), - ('cancelling', cancelling_value), - ('other', other_value)]) -def test_send_real_device_online_verbose(mock_boto3_client, var_status, - var_result, real_device_online_setup): - - (qtarntask, creds, s3_folder, info, search_value, device_value, - res_completed, results_dict) = real_device_online_setup +@pytest.mark.parametrize( + "var_status, var_result", + [ + ('completed', completed_value), + ('failed', failed_value), + ('cancelling', cancelling_value), + ('other', other_value), + ], +) +def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_result, real_device_online_setup): + + ( + qtarntask, + creds, + s3_folder, + info, + search_value, + device_value, + res_completed, + results_dict, + ) = real_device_online_setup mock_boto3_client.return_value = mock_boto3_client mock_boto3_client.search_devices.return_value = search_value @@ -199,34 +208,35 @@ def test_send_real_device_online_verbose(mock_boto3_client, var_status, # statuses in var_status for the tests if var_status == 'completed': - res = _awsbraket_boto3_client.send(info, - device='name2', - credentials=creds, - s3_folder=s3_folder, - verbose=True) + res = _awsbraket_boto3_client.send(info, device='name2', credentials=creds, s3_folder=s3_folder, verbose=True) assert res == res_completed else: with pytest.raises(Exception) as exinfo: - _awsbraket_boto3_client.send(info, - device='name2', - credentials=creds, - s3_folder=s3_folder, - verbose=True, - num_retries=2) + _awsbraket_boto3_client.send( + info, + device='name2', + credentials=creds, + s3_folder=s3_folder, + verbose=True, + num_retries=2, + ) print(exinfo.value) if var_status == 'failed': - assert str(exinfo.value) == \ - "Error while running the code: FAILED. The failure \ + assert ( + str(exinfo.value) + == "Error while running the code: FAILED. The failure \ reason was: This is a failure reason." + ) if var_status == 'cancelling': - assert str(exinfo.value) == \ - "The job received a CANCEL operation: CANCELLING." + assert str(exinfo.value) == "The job received a CANCEL operation: CANCELLING." if var_status == 'other': - assert str(exinfo.value) == \ - "Timeout. The Arn of your submitted job \ + assert ( + str(exinfo.value) + == "Timeout. The Arn of your submitted job \ is arn:aws:braket:us-east-1:id:taskuuid \ and the status of the job is OTHER." + ) # ============================================================================== @@ -234,37 +244,38 @@ def test_send_real_device_online_verbose(mock_boto3_client, var_status, @has_boto3 @patch('boto3.client') -@pytest.mark.parametrize("var_error", [('AccessDeniedException'), - ('DeviceOfflineException'), - ('InternalServiceException'), - ('ServiceQuotaExceededException'), - ('ValidationException')]) -def test_send_that_errors_are_caught(mock_boto3_client, var_error, - send_that_error_setup): +@pytest.mark.parametrize( + "var_error", + [ + ('AccessDeniedException'), + ('DeviceOfflineException'), + ('InternalServiceException'), + ('ServiceQuotaExceededException'), + ('ValidationException'), + ], +) +def test_send_that_errors_are_caught(mock_boto3_client, var_error, send_that_error_setup): creds, s3_folder, info, search_value, device_value = send_that_error_setup mock_boto3_client.return_value = mock_boto3_client mock_boto3_client.search_devices.return_value = search_value mock_boto3_client.get_device.return_value = device_value - mock_boto3_client.create_quantum_task.side_effect = \ - botocore.exceptions.ClientError( - {"Error": { - "Code": var_error, - "Message": "Msg error for "+var_error}}, "create_quantum_task") - - with pytest.raises(botocore.exceptions.ClientError) as exinfo: - _awsbraket_boto3_client.send(info, - device='name2', - credentials=creds, - s3_folder=s3_folder, - num_retries=2) - - with pytest.raises(_awsbraket_boto3_client.DeviceOfflineError) as exinfo: - _awsbraket_boto3_client.send(info, - device='unknown', - credentials=creds, - s3_folder=s3_folder, - num_retries=2) + mock_boto3_client.create_quantum_task.side_effect = botocore.exceptions.ClientError( + {"Error": {"Code": var_error, "Message": "Msg error for " + var_error}}, + "create_quantum_task", + ) + + with pytest.raises(botocore.exceptions.ClientError): + _awsbraket_boto3_client.send(info, device='name2', credentials=creds, s3_folder=s3_folder, num_retries=2) + + with pytest.raises(_awsbraket_boto3_client.DeviceOfflineError): + _awsbraket_boto3_client.send( + info, + device='unknown', + credentials=creds, + s3_folder=s3_folder, + num_retries=2, + ) # ============================================================================== @@ -273,17 +284,15 @@ def test_send_that_errors_are_caught(mock_boto3_client, var_error, @has_boto3 @patch('boto3.client') @pytest.mark.parametrize("var_error", [('ResourceNotFoundException')]) -def test_retrieve_error_arn_not_exist(mock_boto3_client, var_error, arntask, - creds): +def test_retrieve_error_arn_not_exist(mock_boto3_client, var_error, arntask, creds): mock_boto3_client.return_value = mock_boto3_client - mock_boto3_client.get_quantum_task.side_effect = \ - botocore.exceptions.ClientError( - {"Error": { - "Code": var_error, - "Message": "Msg error for "+var_error}}, "get_quantum_task") + mock_boto3_client.get_quantum_task.side_effect = botocore.exceptions.ClientError( + {"Error": {"Code": var_error, "Message": "Msg error for " + var_error}}, + "get_quantum_task", + ) - with pytest.raises(botocore.exceptions.ClientError) as exinfo: + with pytest.raises(botocore.exceptions.ClientError): _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask) diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py index a46cc7c77..8092a4a45 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test_fixtures.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,7 +44,7 @@ except ImportError: class StreamingBody: - def __init__(self, d, l): + def __init__(self, raw_stream, content_length): pass @@ -71,39 +72,36 @@ def s3_folder(): @pytest.fixture def info(): return { - 'circuit': - '{"braketSchemaHeader":' + 'circuit': '{"braketSchemaHeader":' '{"name": "braket.ir.jaqcd.program", "version": "1"}, ' '"results": [], "basis_rotation_instructions": [], ' '"instructions": [{"target": 0, "type": "h"}, {\ "target": 1, "type": "h"}, {\ "control": 1, "target": 2, "type": "cnot"}]}', - 'nq': - 10, - 'shots': - 1, - 'backend': { - 'name': 'name2' - } + 'nq': 10, + 'shots': 1, + 'backend': {'name': 'name2'}, } @pytest.fixture def results_json(): - return json.dumps({ - "braketSchemaHeader": { - "name": "braket.task_result.gate_model_task_result", - "version": "1" - }, - "measurementProbabilities": { - "000": 0.1, - "010": 0.4, - "110": 0.1, - "001": 0.1, - "111": 0.3 - }, - "measuredQubits": [0, 1, 2], - }) + return json.dumps( + { + "braketSchemaHeader": { + "name": "braket.task_result.gate_model_task_result", + "version": "1", + }, + "measurementProbabilities": { + "000": 0.1, + "010": 0.4, + "110": 0.1, + "001": 0.1, + "111": 0.3, + }, + "measuredQubits": [0, 1, 2], + } + ) @pytest.fixture @@ -114,7 +112,7 @@ def results_dict(results_json): 'RequestId': 'CF4CAA48CC18836C', 'HTTPHeaders': {}, }, - 'Body': body + 'Body': body, } @@ -161,63 +159,62 @@ def search_value(): @pytest.fixture def device_value_devicecapabilities(): - return json.dumps({ - "braketSchemaHeader": { - "name": "braket.device_schema.rigetti.rigetti_device_capabilities", - "version": "1", - }, - "service": { - "executionWindows": [{ - "executionDay": "Everyday", - "windowStartHour": "11:00", - "windowEndHour": "12:00", - }], - "shotsRange": [1, 10], - "deviceLocation": - "us-east-1", - }, - "action": { - "braket.ir.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - "supportedOperations": ["H"], - } - }, - "paradigm": { - "qubitCount": 30, - "nativeGateSet": ["ccnot", "cy"], - "connectivity": { - "fullyConnected": False, - "connectivityGraph": { - "1": ["2", "3"] - } + return json.dumps( + { + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", }, - }, - "deviceParameters": { - "properties": { - "braketSchemaHeader": { - "const": { - "name": - "braket.device_schema.rigetti.rigetti_device_parameters", - "version": "1" + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", } + ], + "shotsRange": [1, 10], + "deviceLocation": "us-east-1", + }, + "action": { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], } }, - "definitions": { - "GateModelParameters": { - "properties": { - "braketSchemaHeader": { - "const": { - "name": - "braket.device_schema.gate_model_parameters", - "version": "1" + "paradigm": { + "qubitCount": 30, + "nativeGateSet": ["ccnot", "cy"], + "connectivity": { + "fullyConnected": False, + "connectivityGraph": {"1": ["2", "3"]}, + }, + }, + "deviceParameters": { + "properties": { + "braketSchemaHeader": { + "const": { + "name": "braket.device_schema.rigetti.rigetti_device_parameters", + "version": "1", + } + } + }, + "definitions": { + "GateModelParameters": { + "properties": { + "braketSchemaHeader": { + "const": { + "name": "braket.device_schema.gate_model_parameters", + "version": "1", + } } } } - } + }, }, - }, - }) + } + ) @pytest.fixture @@ -241,51 +238,44 @@ def devicelist_result(): 'nq': 30, 'version': '1', 'deviceParameters': { - 'name': - 'braket.device_schema.rigetti.rigetti_device_parameters', - 'version': '1' + 'name': 'braket.device_schema.rigetti.rigetti_device_parameters', + 'version': '1', }, 'deviceModelParameters': { 'name': 'braket.device_schema.gate_model_parameters', - 'version': '1' - } + 'version': '1', + }, }, 'name2': { - 'coupling_map': { - '1': ['2', '3'] - }, + 'coupling_map': {'1': ['2', '3']}, 'deviceArn': 'arn2', 'location': 'us-east-1', 'nq': 30, 'version': '1', 'deviceParameters': { - 'name': - 'braket.device_schema.rigetti.rigetti_device_parameters', - 'version': '1' + 'name': 'braket.device_schema.rigetti.rigetti_device_parameters', + 'version': '1', }, 'deviceModelParameters': { 'name': 'braket.device_schema.gate_model_parameters', - 'version': '1' - } + 'version': '1', + }, }, 'name3': { - 'coupling_map': { - '1': ['2', '3'] - }, + 'coupling_map': {'1': ['2', '3']}, 'deviceArn': 'arn3', 'location': 'us-east-1', 'nq': 30, 'version': '1', 'deviceParameters': { - 'name': - 'braket.device_schema.rigetti.rigetti_device_parameters', - 'version': '1' + 'name': 'braket.device_schema.rigetti.rigetti_device_parameters', + 'version': '1', }, 'deviceModelParameters': { 'name': 'braket.device_schema.gate_model_parameters', - 'version': '1' - } - } + 'version': '1', + }, + }, } @@ -303,8 +293,7 @@ def retrieve_setup(arntask, creds, device_value, res_completed, results_dict): @pytest.fixture(params=["qpu", "sim"]) -def retrieve_devicetypes_setup(request, arntask, creds, results_json, - device_value_devicecapabilities): +def retrieve_devicetypes_setup(request, arntask, creds, results_json, device_value_devicecapabilities): if request.param == "qpu": body_qpu = StreamingBody(StringIO(results_json), len(results_json)) results_dict = { @@ -312,7 +301,7 @@ def retrieve_devicetypes_setup(request, arntask, creds, results_json, 'RequestId': 'CF4CAA48CC18836C', 'HTTPHeaders': {}, }, - 'Body': body_qpu + 'Body': body_qpu, } device_value = { @@ -323,33 +312,36 @@ def retrieve_devicetypes_setup(request, arntask, creds, results_json, "deviceCapabilities": device_value_devicecapabilities, } - res_completed = { - "000": 0.1, - "010": 0.4, - "110": 0.1, - "001": 0.1, - "111": 0.3 - } + res_completed = {"000": 0.1, "010": 0.4, "110": 0.1, "001": 0.1, "111": 0.3} else: - results_json_simulator = json.dumps({ - "braketSchemaHeader": { - "name": "braket.task_result.gate_model_task_result", - "version": "1" - }, - "measurements": [[0, 0], [0, 1], [1, 1], [0, 1], [0, 1], [1, 1], - [1, 1], [1, 1], [1, 1], [1, 1]], - "measuredQubits": [0, 1], - }) - body_simulator = \ - StreamingBody( - StringIO(results_json_simulator), len( - results_json_simulator)) + results_json_simulator = json.dumps( + { + "braketSchemaHeader": { + "name": "braket.task_result.gate_model_task_result", + "version": "1", + }, + "measurements": [ + [0, 0], + [0, 1], + [1, 1], + [0, 1], + [0, 1], + [1, 1], + [1, 1], + [1, 1], + [1, 1], + [1, 1], + ], + "measuredQubits": [0, 1], + } + ) + body_simulator = StreamingBody(StringIO(results_json_simulator), len(results_json_simulator)) results_dict = { 'ResponseMetadata': { 'RequestId': 'CF4CAA48CC18836C', 'HTTPHeaders': {}, }, - 'Body': body_simulator + 'Body': body_simulator, } device_value = { @@ -367,27 +359,30 @@ def retrieve_devicetypes_setup(request, arntask, creds, results_json, @pytest.fixture def send_too_many_setup(creds, s3_folder, search_value, device_value): info_too_much = { - 'circuit': - '{"braketSchemaHeader":' + 'circuit': '{"braketSchemaHeader":' '{"name": "braket.ir.jaqcd.program", "version": "1"}, ' '"results": [], "basis_rotation_instructions": [], ' '"instructions": [{"target": 0, "type": "h"}, {\ "target": 1, "type": "h"}, {\ "control": 1, "target": 2, "type": "cnot"}]}', - 'nq': - 100, - 'shots': - 1, - 'backend': { - 'name': 'name2' - } + 'nq': 100, + 'shots': 1, + 'backend': {'name': 'name2'}, } return creds, s3_folder, search_value, device_value, info_too_much @pytest.fixture -def real_device_online_setup(arntask, creds, s3_folder, info, search_value, - device_value, res_completed, results_json): +def real_device_online_setup( + arntask, + creds, + s3_folder, + info, + search_value, + device_value, + res_completed, + results_json, +): qtarntask = {'quantumTaskArn': arntask} body = StreamingBody(StringIO(results_json), len(results_json)) results_dict = { @@ -395,11 +390,19 @@ def real_device_online_setup(arntask, creds, s3_folder, info, search_value, 'RequestId': 'CF4CAA48CC18836C', 'HTTPHeaders': {}, }, - 'Body': body + 'Body': body, } - return (qtarntask, creds, s3_folder, info, search_value, device_value, - res_completed, results_dict) + return ( + qtarntask, + creds, + s3_folder, + info, + search_value, + device_value, + res_completed, + results_dict, + ) @pytest.fixture diff --git a/projectq/backends/_awsbraket/_awsbraket_test.py b/projectq/backends/_awsbraket/_awsbraket_test.py index dcb33f515..d82274cbe 100644 --- a/projectq/backends/_awsbraket/_awsbraket_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,36 +15,62 @@ """ Test for projectq.backends._awsbraket._awsbraket.py""" import pytest -from unittest.mock import MagicMock, Mock, patch +from unittest.mock import patch import copy import math -from projectq.setups import restrictedgateset from projectq import MainEngine from projectq.types import WeakQubitRef, Qubit -from projectq.cengines import (BasicMapperEngine, DummyEngine, AutoReplacer, - DecompositionRuleSet) +from projectq.cengines import ( + BasicMapperEngine, + DummyEngine, + AutoReplacer, + DecompositionRuleSet, +) from projectq.cengines._replacer import NoGateDecompositionError -from projectq.ops import (R, Swap, H, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z, - CNOT, SqrtX, MatrixGate, Entangle, Ph, NOT, C, - Measure, Allocate, Deallocate, Barrier, All, Command) - -from ._awsbraket_test_fixtures import * +from projectq.ops import ( + R, + Swap, + H, + Rx, + Ry, + Rz, + S, + Sdag, + T, + Tdag, + X, + Y, + Z, + CNOT, + SqrtX, + MatrixGate, + Entangle, + Ph, + NOT, + C, + Measure, + Allocate, + Deallocate, + Barrier, + All, + Command, +) + +from ._awsbraket_test_fixtures import * # noqa: F401,F403 # ============================================================================== _has_boto3 = True try: - from botocore.response import StreamingBody import botocore from projectq.backends._awsbraket import _awsbraket except ImportError: _has_boto3 = False -has_boto3 = pytest.mark.skipif(not _has_boto3, - reason="boto3 package is not installed") +has_boto3 = pytest.mark.skipif(not _has_boto3, reason="boto3 package is not installed") # ============================================================================== @@ -84,38 +111,68 @@ def receive(self, command_list): @has_boto3 -@pytest.mark.parametrize("single_qubit_gate_aspen, is_available_aspen", - [(X, True), (Y, True), (Z, True), (H, True), - (T, True), (Tdag, True), (S, True), (Sdag, True), - (Allocate, True), (Deallocate, True), (SqrtX, False), - (Measure, True), (Rx(0.5), True), (Ry(0.5), True), - (Rz(0.5), True), (Ph(0.5), False), (R(0.5), True), - (Barrier, True), (Entangle, False)]) -def test_awsbraket_backend_is_available_aspen(single_qubit_gate_aspen, - is_available_aspen): +@pytest.mark.parametrize( + "single_qubit_gate_aspen, is_available_aspen", + [ + (X, True), + (Y, True), + (Z, True), + (H, True), + (T, True), + (Tdag, True), + (S, True), + (Sdag, True), + (Allocate, True), + (Deallocate, True), + (SqrtX, False), + (Measure, True), + (Rx(0.5), True), + (Ry(0.5), True), + (Rz(0.5), True), + (Ph(0.5), False), + (R(0.5), True), + (Barrier, True), + (Entangle, False), + ], +) +def test_awsbraket_backend_is_available_aspen(single_qubit_gate_aspen, is_available_aspen): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() - aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, - device='Aspen-8') - cmd = Command(eng, single_qubit_gate_aspen, (qubit1, )) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, device='Aspen-8') + cmd = Command(eng, single_qubit_gate_aspen, (qubit1,)) assert aws_backend.is_available(cmd) == is_available_aspen @has_boto3 -@pytest.mark.parametrize("single_qubit_gate_ionq, is_available_ionq", - [(X, True), (Y, True), (Z, True), (H, True), - (T, True), (Tdag, True), (S, True), (Sdag, True), - (Allocate, True), (Deallocate, True), (SqrtX, True), - (Measure, True), (Rx(0.5), True), (Ry(0.5), True), - (Rz(0.5), True), (Ph(0.5), False), (R(0.5), False), - (Barrier, True), (Entangle, False)]) -def test_awsbraket_backend_is_available_ionq(single_qubit_gate_ionq, - is_available_ionq): +@pytest.mark.parametrize( + "single_qubit_gate_ionq, is_available_ionq", + [ + (X, True), + (Y, True), + (Z, True), + (H, True), + (T, True), + (Tdag, True), + (S, True), + (Sdag, True), + (Allocate, True), + (Deallocate, True), + (SqrtX, True), + (Measure, True), + (Rx(0.5), True), + (Ry(0.5), True), + (Rz(0.5), True), + (Ph(0.5), False), + (R(0.5), False), + (Barrier, True), + (Entangle, False), + ], +) +def test_awsbraket_backend_is_available_ionq(single_qubit_gate_ionq, is_available_ionq): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() - aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, - device='IonQ Device') - cmd = Command(eng, single_qubit_gate_ionq, (qubit1, )) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, device='IonQ Device') + cmd = Command(eng, single_qubit_gate_ionq, (qubit1,)) assert aws_backend.is_available(cmd) == is_available_ionq @@ -137,111 +194,129 @@ def test_awsbraket_backend_is_available_ionq(single_qubit_gate_ionq, (Measure, True), (Rx(0.5), True), # use MatrixGate as unitary gate - (MatrixGate([[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0] - ]), False), + (MatrixGate([[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]), False), (Ry(0.5), True), (Rz(0.5), True), (Ph(0.5), False), (R(0.5), True), (Barrier, True), - (Entangle, False) - ]) -def test_awsbraket_backend_is_available_sv1(single_qubit_gate_sv1, - is_available_sv1): + (Entangle, False), + ], +) +def test_awsbraket_backend_is_available_sv1(single_qubit_gate_sv1, is_available_sv1): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() aws_backend = _awsbraket.AWSBraketBackend(use_hardware=False) - cmd = Command(eng, single_qubit_gate_sv1, (qubit1, )) + cmd = Command(eng, single_qubit_gate_sv1, (qubit1,)) assert aws_backend.is_available(cmd) == is_available_sv1 @has_boto3 -@pytest.mark.parametrize("num_ctrl_qubits_aspen, is_available_aspen", - [(0, True), (1, True), (2, True), (3, False)]) -def test_awsbraket_backend_is_available_control_not_aspen( - num_ctrl_qubits_aspen, is_available_aspen): +@pytest.mark.parametrize( + "num_ctrl_qubits_aspen, is_available_aspen", + [(0, True), (1, True), (2, True), (3, False)], +) +def test_awsbraket_backend_is_available_control_not_aspen(num_ctrl_qubits_aspen, is_available_aspen): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() qureg = eng.allocate_qureg(num_ctrl_qubits_aspen) - aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, - device='Aspen-8') - cmd = Command(eng, X, (qubit1, ), controls=qureg) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, device='Aspen-8') + cmd = Command(eng, X, (qubit1,), controls=qureg) assert aws_backend.is_available(cmd) == is_available_aspen @has_boto3 -@pytest.mark.parametrize("num_ctrl_qubits_ionq, is_available_ionq", - [(0, True), (1, True), (2, False), (3, False)]) -def test_awsbraket_backend_is_available_control_not_ionq( - num_ctrl_qubits_ionq, is_available_ionq): +@pytest.mark.parametrize( + "num_ctrl_qubits_ionq, is_available_ionq", + [(0, True), (1, True), (2, False), (3, False)], +) +def test_awsbraket_backend_is_available_control_not_ionq(num_ctrl_qubits_ionq, is_available_ionq): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() qureg = eng.allocate_qureg(num_ctrl_qubits_ionq) - aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, - device='IonQ Device') - cmd = Command(eng, X, (qubit1, ), controls=qureg) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, device='IonQ Device') + cmd = Command(eng, X, (qubit1,), controls=qureg) assert aws_backend.is_available(cmd) == is_available_ionq @has_boto3 -@pytest.mark.parametrize("num_ctrl_qubits_sv1, is_available_sv1", [(0, True), - (1, True), - (2, True), - (3, False)]) -def test_awsbraket_backend_is_available_control_not_sv1( - num_ctrl_qubits_sv1, is_available_sv1): +@pytest.mark.parametrize( + "num_ctrl_qubits_sv1, is_available_sv1", + [(0, True), (1, True), (2, True), (3, False)], +) +def test_awsbraket_backend_is_available_control_not_sv1(num_ctrl_qubits_sv1, is_available_sv1): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() qureg = eng.allocate_qureg(num_ctrl_qubits_sv1) aws_backend = _awsbraket.AWSBraketBackend(use_hardware=False) - cmd = Command(eng, X, (qubit1, ), controls=qureg) + cmd = Command(eng, X, (qubit1,), controls=qureg) assert aws_backend.is_available(cmd) == is_available_sv1 @has_boto3 -@pytest.mark.parametrize("ctrl_singlequbit_aspen, is_available_aspen", - [(X, True), (Y, False), (Z, True), (R(0.5), True), - (Rx(0.5), False), (Ry(0.5), False), (Rz(0.5), False), - (NOT, True)]) -def test_awsbraket_backend_is_available_control_singlequbit_aspen( - ctrl_singlequbit_aspen, is_available_aspen): +@pytest.mark.parametrize( + "ctrl_singlequbit_aspen, is_available_aspen", + [ + (X, True), + (Y, False), + (Z, True), + (R(0.5), True), + (Rx(0.5), False), + (Ry(0.5), False), + (Rz(0.5), False), + (NOT, True), + ], +) +def test_awsbraket_backend_is_available_control_singlequbit_aspen(ctrl_singlequbit_aspen, is_available_aspen): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() qureg = eng.allocate_qureg(1) - aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, - device='Aspen-8') - cmd = Command(eng, ctrl_singlequbit_aspen, (qubit1, ), controls=qureg) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, device='Aspen-8') + cmd = Command(eng, ctrl_singlequbit_aspen, (qubit1,), controls=qureg) assert aws_backend.is_available(cmd) == is_available_aspen @has_boto3 -@pytest.mark.parametrize("ctrl_singlequbit_ionq, is_available_ionq", - [(X, True), (Y, False), (Z, False), (R(0.5), False), - (Rx(0.5), False), (Ry(0.5), False), - (Rz(0.5), False)]) -def test_awsbraket_backend_is_available_control_singlequbit_ionq( - ctrl_singlequbit_ionq, is_available_ionq): +@pytest.mark.parametrize( + "ctrl_singlequbit_ionq, is_available_ionq", + [ + (X, True), + (Y, False), + (Z, False), + (R(0.5), False), + (Rx(0.5), False), + (Ry(0.5), False), + (Rz(0.5), False), + ], +) +def test_awsbraket_backend_is_available_control_singlequbit_ionq(ctrl_singlequbit_ionq, is_available_ionq): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() qureg = eng.allocate_qureg(1) - aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, - device='IonQ Device') - cmd = Command(eng, ctrl_singlequbit_ionq, (qubit1, ), controls=qureg) + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, device='IonQ Device') + cmd = Command(eng, ctrl_singlequbit_ionq, (qubit1,), controls=qureg) assert aws_backend.is_available(cmd) == is_available_ionq @has_boto3 -@pytest.mark.parametrize("ctrl_singlequbit_sv1, is_available_sv1", - [(X, True), (Y, True), (Z, True), (R(0.5), True), - (Rx(0.5), False), (Ry(0.5), False), - (Rz(0.5), False)]) -def test_awsbraket_backend_is_available_control_singlequbit_sv1( - ctrl_singlequbit_sv1, is_available_sv1): +@pytest.mark.parametrize( + "ctrl_singlequbit_sv1, is_available_sv1", + [ + (X, True), + (Y, True), + (Z, True), + (R(0.5), True), + (Rx(0.5), False), + (Ry(0.5), False), + (Rz(0.5), False), + ], +) +def test_awsbraket_backend_is_available_control_singlequbit_sv1(ctrl_singlequbit_sv1, is_available_sv1): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() qureg = eng.allocate_qureg(1) aws_backend = _awsbraket.AWSBraketBackend(use_hardware=False) - cmd = Command(eng, ctrl_singlequbit_sv1, (qubit1, ), controls=qureg) + cmd = Command(eng, ctrl_singlequbit_sv1, (qubit1,), controls=qureg) assert aws_backend.is_available(cmd) == is_available_sv1 @@ -250,10 +325,9 @@ def test_awsbraket_backend_is_available_swap_aspen(): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() qubit2 = eng.allocate_qubit() - aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, - device='Aspen-8') + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, device='Aspen-8') cmd = Command(eng, Swap, (qubit1, qubit2)) - assert aws_backend.is_available(cmd) == True + assert aws_backend.is_available(cmd) @has_boto3 @@ -261,10 +335,9 @@ def test_awsbraket_backend_is_available_swap_ionq(): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit1 = eng.allocate_qubit() qubit2 = eng.allocate_qubit() - aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, - device='IonQ Device') + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, device='IonQ Device') cmd = Command(eng, Swap, (qubit1, qubit2)) - assert aws_backend.is_available(cmd) == True + assert aws_backend.is_available(cmd) @has_boto3 @@ -274,7 +347,7 @@ def test_awsbraket_backend_is_available_swap_sv1(): qubit2 = eng.allocate_qubit() aws_backend = _awsbraket.AWSBraketBackend(use_hardware=False) cmd = Command(eng, Swap, (qubit1, qubit2)) - assert aws_backend.is_available(cmd) == True + assert aws_backend.is_available(cmd) @has_boto3 @@ -283,10 +356,9 @@ def test_awsbraket_backend_is_available_control_swap_aspen(): qubit1 = eng.allocate_qubit() qubit2 = eng.allocate_qubit() qureg = eng.allocate_qureg(1) - aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, - device='Aspen-8') + aws_backend = _awsbraket.AWSBraketBackend(use_hardware=True, device='Aspen-8') cmd = Command(eng, Swap, (qubit1, qubit2), controls=qureg) - assert aws_backend.is_available(cmd) == True + assert aws_backend.is_available(cmd) @has_boto3 @@ -297,7 +369,7 @@ def test_awsbraket_backend_is_available_control_swap_sv1(): qureg = eng.allocate_qureg(1) aws_backend = _awsbraket.AWSBraketBackend(use_hardware=False) cmd = Command(eng, Swap, (qubit1, qubit2), controls=qureg) - assert aws_backend.is_available(cmd) == True + assert aws_backend.is_available(cmd) ''' @@ -322,8 +394,8 @@ def test_awsbraket_empty_circuit(): def test_awsbraket_invalid_command(): backend = _awsbraket.AWSBraketBackend(use_hardware=True, verbose=True) qb = WeakQubitRef(None, 1) - cmd = Command(None, gate=SqrtX, qubits=[(qb, )]) - with pytest.raises(Exception) as excinfo: + cmd = Command(None, gate=SqrtX, qubits=[(qb,)]) + with pytest.raises(Exception): backend.receive([cmd]) @@ -339,19 +411,19 @@ def test_awsbraket_sent_error(mock_boto3_client, sent_error_setup): mock_boto3_client.return_value = mock_boto3_client mock_boto3_client.search_devices.return_value = search_value mock_boto3_client.get_device.return_value = device_value - mock_boto3_client.create_quantum_task.side_effect = \ - botocore.exceptions.ClientError( - {"Error": {"Code": var_error, - "Message": "Msg error for "+var_error}}, - "create_quantum_task" - ) - - backend = _awsbraket.AWSBraketBackend(verbose=True, - credentials=creds, - s3_folder=s3_folder, - use_hardware=True, - device='Aspen-8', - num_runs=10) + mock_boto3_client.create_quantum_task.side_effect = botocore.exceptions.ClientError( + {"Error": {"Code": var_error, "Message": "Msg error for " + var_error}}, + "create_quantum_task", + ) + + backend = _awsbraket.AWSBraketBackend( + verbose=True, + credentials=creds, + s3_folder=s3_folder, + use_hardware=True, + device='Aspen-8', + num_runs=10, + ) eng = MainEngine(backend=backend, verbose=True) qubit = eng.allocate_qubit() Rx(0.5) | qubit @@ -367,13 +439,12 @@ def test_awsbraket_sent_error(mock_boto3_client, sent_error_setup): @has_boto3 def test_awsbraket_sent_error_2(): - backend = _awsbraket.AWSBraketBackend(verbose=True, - use_hardware=True, - device='Aspen-8') + backend = _awsbraket.AWSBraketBackend(verbose=True, use_hardware=True, device='Aspen-8') eng = MainEngine( backend=backend, engine_list=[AutoReplacer(DecompositionRuleSet())], - verbose=True) + verbose=True, + ) qubit = eng.allocate_qubit() Rx(math.pi) | qubit @@ -392,18 +463,14 @@ def test_awsbraket_sent_error_2(): @has_boto3 @patch('boto3.client') def test_awsbraket_retrieve(mock_boto3_client, retrieve_setup): - (arntask, creds, completed_value, device_value, - results_dict) = retrieve_setup + (arntask, creds, completed_value, device_value, results_dict) = retrieve_setup mock_boto3_client.return_value = mock_boto3_client mock_boto3_client.get_quantum_task.return_value = completed_value mock_boto3_client.get_device.return_value = device_value mock_boto3_client.get_object.return_value = results_dict - backend = _awsbraket.AWSBraketBackend(retrieve_execution=arntask, - credentials=creds, - num_retries=2, - verbose=True) + backend = _awsbraket.AWSBraketBackend(retrieve_execution=arntask, credentials=creds, num_retries=2, verbose=True) mapper = BasicMapperEngine() res = dict() @@ -434,10 +501,16 @@ def test_awsbraket_retrieve(mock_boto3_client, retrieve_setup): @has_boto3 @patch('boto3.client') -def test_awsbraket_backend_functional_test(mock_boto3_client, functional_setup, - mapper): - (creds, s3_folder, search_value, device_value, qtarntask, completed_value, - results_dict) = functional_setup +def test_awsbraket_backend_functional_test(mock_boto3_client, functional_setup, mapper): + ( + creds, + s3_folder, + search_value, + device_value, + qtarntask, + completed_value, + results_dict, + ) = functional_setup mock_boto3_client.return_value = mock_boto3_client mock_boto3_client.search_devices.return_value = search_value @@ -446,19 +519,20 @@ def test_awsbraket_backend_functional_test(mock_boto3_client, functional_setup, mock_boto3_client.get_quantum_task.return_value = completed_value mock_boto3_client.get_object.return_value = results_dict - backend = _awsbraket.AWSBraketBackend(verbose=True, - credentials=creds, - s3_folder=s3_folder, - use_hardware=True, - device='Aspen-8', - num_runs=10, - num_retries=2) + backend = _awsbraket.AWSBraketBackend( + verbose=True, + credentials=creds, + s3_folder=s3_folder, + use_hardware=True, + device='Aspen-8', + num_runs=10, + num_retries=2, + ) # no circuit has been executed -> raises exception with pytest.raises(RuntimeError): backend.get_probabilities([]) - from projectq.setups.default import get_engine_list - from projectq.backends import CommandPrinter, ResourceCounter + from projectq.backends import ResourceCounter rcount = ResourceCounter() engine_list = [rcount] @@ -503,10 +577,16 @@ def test_awsbraket_backend_functional_test(mock_boto3_client, functional_setup, @has_boto3 @patch('boto3.client') -def test_awsbraket_functional_test_as_engine(mock_boto3_client, - functional_setup): - (creds, s3_folder, search_value, device_value, qtarntask, completed_value, - results_dict) = functional_setup +def test_awsbraket_functional_test_as_engine(mock_boto3_client, functional_setup): + ( + creds, + s3_folder, + search_value, + device_value, + qtarntask, + completed_value, + results_dict, + ) = functional_setup mock_boto3_client.return_value = mock_boto3_client mock_boto3_client.search_devices.return_value = search_value @@ -515,25 +595,22 @@ def test_awsbraket_functional_test_as_engine(mock_boto3_client, mock_boto3_client.get_quantum_task.return_value = completed_value mock_boto3_client.get_object.return_value = copy.deepcopy(results_dict) - backend = _awsbraket.AWSBraketBackend(verbose=True, - credentials=creds, - s3_folder=s3_folder, - use_hardware=True, - device='Aspen-8', - num_runs=10, - num_retries=2) + backend = _awsbraket.AWSBraketBackend( + verbose=True, + credentials=creds, + s3_folder=s3_folder, + use_hardware=True, + device='Aspen-8', + num_runs=10, + num_retries=2, + ) # no circuit has been executed -> raises exception with pytest.raises(RuntimeError): backend.get_probabilities([]) - from projectq.setups.default import get_engine_list - from projectq.backends import CommandPrinter, ResourceCounter - - eng = MainEngine(backend=DummyEngine(save_commands=True), - engine_list=[backend], - verbose=True) + eng = MainEngine(backend=DummyEngine(save_commands=True), engine_list=[backend], verbose=True) - unused_qubit = eng.allocate_qubit() + unused_qubit = eng.allocate_qubit() # noqa: F841 qureg = eng.allocate_qureg(3) H | qureg[0] diff --git a/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py index 248d1d7d9..894e4dea6 100644 --- a/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py +++ b/projectq/backends/_awsbraket/_awsbraket_test_fixtures.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,7 +38,7 @@ except ImportError: class StreamingBody: - def __init__(self, d, l): + def __init__(self, raw_stream, content_length): pass @@ -64,63 +65,62 @@ def s3_folder(): @pytest.fixture def device_value(): - device_value_devicecapabilities = json.dumps({ - "braketSchemaHeader": { - "name": "braket.device_schema.rigetti.rigetti_device_capabilities", - "version": "1", - }, - "service": { - "executionWindows": [{ - "executionDay": "Everyday", - "windowStartHour": "11:00", - "windowEndHour": "12:00", - }], - "shotsRange": [1, 10], - "deviceLocation": - "us-east-1", - }, - "action": { - "braket.ir.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - "supportedOperations": ["H"], - } - }, - "paradigm": { - "qubitCount": 30, - "nativeGateSet": ["ccnot", "cy"], - "connectivity": { - "fullyConnected": False, - "connectivityGraph": { - "1": ["2", "3"] - } + device_value_devicecapabilities = json.dumps( + { + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", }, - }, - "deviceParameters": { - "properties": { - "braketSchemaHeader": { - "const": { - "name": - "braket.device_schema.rigetti.rigetti_device_parameters", - "version": "1" + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", } + ], + "shotsRange": [1, 10], + "deviceLocation": "us-east-1", + }, + "action": { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], } }, - "definitions": { - "GateModelParameters": { - "properties": { - "braketSchemaHeader": { - "const": { - "name": - "braket.device_schema.gate_model_parameters", - "version": "1" + "paradigm": { + "qubitCount": 30, + "nativeGateSet": ["ccnot", "cy"], + "connectivity": { + "fullyConnected": False, + "connectivityGraph": {"1": ["2", "3"]}, + }, + }, + "deviceParameters": { + "properties": { + "braketSchemaHeader": { + "const": { + "name": "braket.device_schema.rigetti.rigetti_device_parameters", + "version": "1", + } + } + }, + "definitions": { + "GateModelParameters": { + "properties": { + "braketSchemaHeader": { + "const": { + "name": "braket.device_schema.gate_model_parameters", + "version": "1", + } } } } - } + }, }, - }, - }) + } + ) return { "deviceName": "Aspen-8", @@ -170,9 +170,7 @@ def completed_value(): 'quantumTaskArn': 'arntask', 'shots': 123, 'status': 'COMPLETED', - 'tags': { - 'tagkey': 'tagvalue' - } + 'tags': {'tagkey': 'tagvalue'}, } @@ -187,25 +185,26 @@ def sent_error_setup(creds, s3_folder, device_value, search_value): @pytest.fixture def results_json(): - return json.dumps({ - "braketSchemaHeader": { - "name": "braket.task_result.gate_model_task_result", - "version": "1" - }, - "measurementProbabilities": { - "0000": 0.04, - "0010": 0.06, - "0110": 0.2, - "0001": 0.3, - "1001": 0.5 - }, - "measuredQubits": [0, 1, 2], - }) + return json.dumps( + { + "braketSchemaHeader": { + "name": "braket.task_result.gate_model_task_result", + "version": "1", + }, + "measurementProbabilities": { + "0000": 0.04, + "0010": 0.06, + "0110": 0.2, + "0001": 0.3, + "1001": 0.5, + }, + "measuredQubits": [0, 1, 2], + } + ) @pytest.fixture -def retrieve_setup(arntask, creds, device_value, completed_value, - results_json): +def retrieve_setup(arntask, creds, device_value, completed_value, results_json): body = StreamingBody(StringIO(results_json), len(results_json)) @@ -214,15 +213,14 @@ def retrieve_setup(arntask, creds, device_value, completed_value, 'RequestId': 'CF4CAA48CC18836C', 'HTTPHeaders': {}, }, - 'Body': body + 'Body': body, } return arntask, creds, completed_value, device_value, results_dict @pytest.fixture -def functional_setup(arntask, creds, s3_folder, search_value, device_value, - completed_value, results_json): +def functional_setup(arntask, creds, s3_folder, search_value, device_value, completed_value, results_json): qtarntask = {'quantumTaskArn': arntask} body2 = StreamingBody(StringIO(results_json), len(results_json)) results_dict = { @@ -230,11 +228,18 @@ def functional_setup(arntask, creds, s3_folder, search_value, device_value, 'RequestId': 'CF4CAA48CC18836C', 'HTTPHeaders': {}, }, - 'Body': body2 + 'Body': body2, } - return (creds, s3_folder, search_value, device_value, qtarntask, - completed_value, results_dict) + return ( + creds, + s3_folder, + search_value, + device_value, + qtarntask, + completed_value, + results_dict, + ) # ============================================================================== diff --git a/projectq/backends/_circuits/__init__.py b/projectq/backends/_circuits/__init__.py index be22d24d2..79ce3a298 100755 --- a/projectq/backends/_circuits/__init__.py +++ b/projectq/backends/_circuits/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,4 +18,3 @@ from ._drawer import CircuitDrawer from ._drawer_matplotlib import CircuitDrawerMatplotlib - diff --git a/projectq/backends/_circuits/_drawer.py b/projectq/backends/_circuits/_drawer.py index 2562a07dd..fd86c92a4 100755 --- a/projectq/backends/_circuits/_drawer.py +++ b/projectq/backends/_circuits/_drawer.py @@ -1,4 +1,5 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# -*- coding: utf-8 -*- +# Copyright 2017, 2021 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. @@ -39,9 +40,12 @@ def __init__(self, gate, lines, ctrl_lines): self.id = -1 def __eq__(self, other): - return (self.gate == other.gate and self.lines == other.lines - and self.ctrl_lines == other.ctrl_lines - and self.id == other.id) + return ( + self.gate == other.gate + and self.lines == other.lines + and self.ctrl_lines == other.ctrl_lines + and self.id == other.id + ) def __ne__(self, other): return not self.__eq__(other) @@ -126,6 +130,7 @@ class CircuitDrawer(BasicEngine): }, """ + def __init__(self, accept_input=False, default_measure=0): """ Initialize a circuit drawing engine. @@ -187,15 +192,16 @@ def set_qubit_locations(self, id_to_loc): needs be called before any gates have been received). """ if len(self._map) > 0: - raise RuntimeError("set_qubit_locations() has to be called before" - " applying gates!") + raise RuntimeError("set_qubit_locations() has to be called before applying gates!") for k in range(min(id_to_loc), max(id_to_loc) + 1): if k not in id_to_loc: - raise RuntimeError("set_qubit_locations(): Invalid id_to_loc " - "mapping provided. All ids in the provided" - " range of qubit ids have to be mapped " - "somewhere.") + raise RuntimeError( + "set_qubit_locations(): Invalid id_to_loc " + "mapping provided. All ids in the provided" + " range of qubit ids have to be mapped " + "somewhere." + ) self._map = id_to_loc def _print_cmd(self, cmd): @@ -228,8 +234,7 @@ def _print_cmd(self, cmd): if self._accept_input: m = None while m not in ('0', '1', 1, 0): - prompt = ("Input measurement result (0 or 1) for " - "qubit " + str(qubit) + ": ") + prompt = "Input measurement result (0 or 1) for qubit " + str(qubit) + ": " m = input(prompt) else: m = self._default_measure @@ -242,8 +247,8 @@ def _print_cmd(self, cmd): lines = [qb.id for qr in cmd.qubits for qb in qr] ctrl_lines = [qb.id for qb in cmd.control_qubits] item = CircuitItem(gate, lines, ctrl_lines) - for l in all_lines: - self._qubit_lines[l].append(item) + for line in all_lines: + self._qubit_lines[line].append(item) self._drawing_order.append(all_lines[0]) @@ -284,9 +289,11 @@ def get_latex(self, ordered=False, draw_gates_in_parallel=True): if ordered: drawing_order = self._drawing_order - return to_latex(qubit_lines, - drawing_order=drawing_order, - draw_gates_in_parallel=draw_gates_in_parallel) + return to_latex( + qubit_lines, + drawing_order=drawing_order, + draw_gates_in_parallel=draw_gates_in_parallel, + ) def receive(self, command_list): """ diff --git a/projectq/backends/_circuits/_drawer_matplotlib.py b/projectq/backends/_circuits/_drawer_matplotlib.py index 1fd61df1f..c26bd0af4 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib.py +++ b/projectq/backends/_circuits/_drawer_matplotlib.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +22,7 @@ import itertools from projectq.cengines import LastEngineException, BasicEngine -from projectq.ops import (FlushGate, Measure, Allocate, Deallocate) +from projectq.ops import FlushGate, Measure, Allocate, Deallocate from projectq.meta import get_control_count from projectq.backends._circuits import to_draw @@ -56,9 +57,11 @@ class CircuitDrawerMatplotlib(BasicEngine): CircuitDrawerMatplotlib is a compiler engine which using Matplotlib library for drawing quantum circuits """ + def __init__(self, accept_input=False, default_measure=0): """ Initialize a circuit drawing engine(mpl) + Args: accept_input (bool): If accept_input is true, the printer queries the user to input measurement results if the CircuitDrawerMPL @@ -123,13 +126,11 @@ def _process(self, cmd): if self._accept_input: measurement = None while measurement not in ('0', '1', 1, 0): - prompt = ("Input measurement result (0 or 1) for " - "qubit " + str(qubit) + ": ") + prompt = "Input measurement result (0 or 1) for qubit {}: ".format(qubit) measurement = input(prompt) else: measurement = self._default_measure - self.main_engine.set_measurement_result( - qubit, int(measurement)) + self.main_engine.set_measurement_result(qubit, int(measurement)) targets = [qubit.id for qureg in cmd.qubits for qubit in qureg] controls = [qubit.id for qubit in cmd.control_qubits] @@ -139,9 +140,7 @@ def _process(self, cmd): # First find out what is the maximum index that this command might # have - max_depth = max( - len(self._qubit_lines[qubit_id]) - for qubit_id in itertools.chain(targets, controls)) + max_depth = max(len(self._qubit_lines[qubit_id]) for qubit_id in itertools.chain(targets, controls)) # If we have a multi-qubit gate, make sure that all the qubit axes # have the same depth. We do that by recalculating the maximum index @@ -151,17 +150,14 @@ def _process(self, cmd): # considering the qubit axes that are between the topmost and # bottommost qubit axes of the current command. if len(targets) + len(controls) > 1: - max_depth = max( - len(self._qubit_lines[qubit_id]) - for qubit_id in self._qubit_lines) + max_depth = max(len(self._qubit_lines[qubit_id]) for qubit_id in self._qubit_lines) for qubit_id in itertools.chain(targets, controls): depth = len(self._qubit_lines[qubit_id]) self._qubit_lines[qubit_id] += [None] * (max_depth - depth) if qubit_id == ref_qubit_id: - self._qubit_lines[qubit_id].append( - (gate_str, targets, controls)) + self._qubit_lines[qubit_id].append((gate_str, targets, controls)) else: self._qubit_lines[qubit_id].append(None) @@ -219,14 +215,15 @@ def draw(self, qubit_labels=None, drawing_order=None, **kwargs): - wire_height (1): Vertical spacing between two qubit wires (roughly in inches) """ - max_depth = max( - len(self._qubit_lines[qubit_id]) for qubit_id in self._qubit_lines) + max_depth = max(len(self._qubit_lines[qubit_id]) for qubit_id in self._qubit_lines) for qubit_id in self._qubit_lines: depth = len(self._qubit_lines[qubit_id]) if depth < max_depth: self._qubit_lines[qubit_id] += [None] * (max_depth - depth) - return to_draw(self._qubit_lines, - qubit_labels=qubit_labels, - drawing_order=drawing_order, - **kwargs) + return to_draw( + self._qubit_lines, + qubit_labels=qubit_labels, + drawing_order=drawing_order, + **kwargs, + ) diff --git a/projectq/backends/_circuits/_drawer_matplotlib_test.py b/projectq/backends/_circuits/_drawer_matplotlib_test.py index a76fbc99b..600ff051c 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib_test.py +++ b/projectq/backends/_circuits/_drawer_matplotlib_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,10 +16,9 @@ Tests for projectq.backends.circuits._drawer.py. """ -import pytest from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import (H, X, Rx, CNOT, Swap, Measure, Command, BasicGate) +from projectq.ops import H, X, Rx, CNOT, Swap, Measure, Command, BasicGate from projectq.types import WeakQubitRef from . import _drawer_matplotlib as _drawer @@ -67,12 +67,12 @@ def test_drawer_isavailable(): qb3 = WeakQubitRef(None, 3) for gate in (X, Rx(1.0)): - for qubits in (([qb0], ), ([qb0, qb1], ), ([qb0, qb1, qb2], )): + for qubits in (([qb0],), ([qb0, qb1],), ([qb0, qb1, qb2],)): print(qubits) cmd = Command(None, gate, qubits) assert drawer.is_available(cmd) - cmd0 = Command(None, X, ([qb0], )) + cmd0 = Command(None, X, ([qb0],)) cmd1 = Command(None, Swap, ([qb0], [qb1])) cmd2 = Command(None, Swap, ([qb0], [qb1]), [qb2]) cmd3 = Command(None, Swap, ([qb0], [qb1]), [qb2, qb3]) @@ -136,13 +136,15 @@ def test_drawer_draw(): qubit_lines = drawer.draw() assert qubit_lines == { - 0: [('H', [0], []), ('X', [0], []), None, ('Swap', [0, 1], []), - ('X', [0], [])], - 1: [('H', [1], []), ('Rx(1.00)', [1], []), ('X', [1], [0]), None, - None], - 2: [('MyGate(1.20)', [2], []), ('MyGate(1.23)', [2], []), + 0: [('H', [0], []), ('X', [0], []), None, ('Swap', [0, 1], []), ('X', [0], [])], + 1: [('H', [1], []), ('Rx(1.00)', [1], []), ('X', [1], [0]), None, None], + 2: [ + ('MyGate(1.20)', [2], []), + ('MyGate(1.23)', [2], []), ('MyGate(1.23,2.35)', [2], []), - ('MyGate(1.23,aaaaa...,bbb,2.34)', [2], []), None] + ('MyGate(1.23,aaaaa...,bbb,2.34)', [2], []), + None, + ], } _drawer.to_draw = old_draw diff --git a/projectq/backends/_circuits/_drawer_test.py b/projectq/backends/_circuits/_drawer_test.py index b73513b64..e308bbf45 100755 --- a/projectq/backends/_circuits/_drawer_test.py +++ b/projectq/backends/_circuits/_drawer_test.py @@ -1,4 +1,5 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# -*- coding: utf-8 -*- +# Copyright 2017, 2021 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. @@ -18,9 +19,7 @@ import pytest from projectq import MainEngine -from projectq.cengines import LastEngineException -from projectq.ops import (H, X, CNOT, Measure) -from projectq.meta import Control +from projectq.ops import H, X, CNOT, Measure import projectq.backends._circuits._drawer as _drawer from projectq.backends._circuits._drawer import CircuitItem, CircuitDrawer @@ -98,7 +97,7 @@ def test_drawer_qubitmapping(): drawer.set_qubit_locations(invalid_mapping) eng = MainEngine(drawer, []) - qubit = eng.allocate_qubit() + qubit = eng.allocate_qubit() # noqa: F841 # mapping has begun --> can't assign it anymore with pytest.raises(RuntimeError): drawer.set_qubit_locations({0: 1, 1: 0}) diff --git a/projectq/backends/_circuits/_plot.py b/projectq/backends/_circuits/_plot.py index f972f605b..97db7646c 100644 --- a/projectq/backends/_circuits/_plot.py +++ b/projectq/backends/_circuits/_plot.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,17 +46,19 @@ # - x_offset # # The rest have misc. units (as defined by matplotlib) -_DEFAULT_PLOT_PARAMS = dict(fontsize=14.0, - column_spacing=.5, - control_radius=0.015, - labels_margin=1, - linewidth=1.0, - not_radius=0.03, - gate_offset=.05, - mgate_width=0.1, - swap_delta=0.02, - x_offset=.05, - wire_height=1) +_DEFAULT_PLOT_PARAMS = dict( + fontsize=14.0, + column_spacing=0.5, + control_radius=0.015, + labels_margin=1, + linewidth=1.0, + not_radius=0.03, + gate_offset=0.05, + mgate_width=0.1, + swap_delta=0.02, + x_offset=0.05, + wire_height=1, +) # ============================================================================== @@ -104,24 +107,18 @@ def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): qubit_labels = {qubit_id: r'$|0\rangle$' for qubit_id in qubit_lines} else: if list(qubit_labels) != list(qubit_lines): - raise RuntimeError('Qubit IDs in qubit_labels do not match ' - + 'qubit IDs in qubit_lines!') + raise RuntimeError('Qubit IDs in qubit_labels do not match qubit IDs in qubit_lines!') if drawing_order is None: n_qubits = len(qubit_lines) - drawing_order = { - qubit_id: n_qubits - qubit_id - 1 - for qubit_id in list(qubit_lines) - } + drawing_order = {qubit_id: n_qubits - qubit_id - 1 for qubit_id in list(qubit_lines)} else: if list(drawing_order) != list(qubit_lines): - raise RuntimeError('Qubit IDs in drawing_order do not match ' - + 'qubit IDs in qubit_lines!') - if (list(sorted(drawing_order.values())) != list( - range(len(drawing_order)))): + raise RuntimeError('Qubit IDs in drawing_order do not match ' + 'qubit IDs in qubit_lines!') + if list(sorted(drawing_order.values())) != list(range(len(drawing_order))): raise RuntimeError( - 'Indices of qubit wires in drawing_order ' - + 'must be between 0 and {}!'.format(len(drawing_order))) + 'Indices of qubit wires in drawing_order must be between 0 and {}!'.format(len(drawing_order)) + ) plot_params = deepcopy(_DEFAULT_PLOT_PARAMS) plot_params.update(kwargs) @@ -130,9 +127,7 @@ def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): wire_height = plot_params['wire_height'] # Grid in inches - wire_grid = np.arange(wire_height, (n_labels + 1) * wire_height, - wire_height, - dtype=float) + wire_grid = np.arange(wire_height, (n_labels + 1) * wire_height, wire_height, dtype=float) fig, axes = create_figure(plot_params) @@ -156,8 +151,7 @@ def to_draw(qubit_lines, qubit_labels=None, drawing_order=None, **kwargs): draw_labels(axes, qubit_labels, drawing_order, wire_grid, plot_params) - draw_gates(axes, qubit_lines, drawing_order, gate_grid, wire_grid, - plot_params) + draw_gates(axes, qubit_lines, drawing_order, gate_grid, wire_grid, plot_params) return fig, axes @@ -185,15 +179,16 @@ def gate_width(axes, gate_str, plot_params): if gate_str == 'Measure': return plot_params['mgate_width'] - obj = axes.text(0, - 0, - gate_str, - visible=True, - bbox=dict(edgecolor='k', facecolor='w', fill=True, lw=1.0), - fontsize=14) + obj = axes.text( + 0, + 0, + gate_str, + visible=True, + bbox=dict(edgecolor='k', facecolor='w', fill=True, lw=1.0), + fontsize=14, + ) obj.figure.canvas.draw() - width = (obj.get_window_extent(obj.figure.canvas.get_renderer()).width - / axes.figure.dpi) + width = obj.get_window_extent(obj.figure.canvas.get_renderer()).width / axes.figure.dpi obj.remove() return width + 2 * plot_params['gate_offset'] @@ -216,19 +211,16 @@ def calculate_gate_grid(axes, qubit_lines, plot_params): depth = len(data[0]) width_list = [ - max( - gate_width(axes, line[idx][0], plot_params) if line[idx] else 0 - for line in data) for idx in range(depth) + max(gate_width(axes, line[idx][0], plot_params) if line[idx] else 0 for line in data) for idx in range(depth) ] gate_grid = np.array([0] * (depth + 1), dtype=float) - + gate_grid[0] = plot_params['labels_margin'] if depth > 0: gate_grid[0] += width_list[0] * 0.5 for idx in range(1, depth): - gate_grid[idx] = gate_grid[idx - 1] + column_spacing + ( - width_list[idx] + width_list[idx - 1]) * 0.5 + gate_grid[idx] = gate_grid[idx - 1] + column_spacing + (width_list[idx] + width_list[idx - 1]) * 0.5 gate_grid[-1] = gate_grid[-2] + column_spacing + width_list[-1] * 0.5 return gate_grid @@ -249,14 +241,16 @@ def text(axes, gate_pos, wire_pos, textstr, plot_params): plot_params (dict): plot parameters box (bool): draw the rectangle box if box is True """ - return axes.text(gate_pos, - wire_pos, - textstr, - color='k', - ha='center', - va='center', - clip_on=True, - size=plot_params['fontsize']) + return axes.text( + gate_pos, + wire_pos, + textstr, + color='k', + ha='center', + va='center', + clip_on=True, + size=plot_params['fontsize'], + ) # ============================================================================== @@ -302,8 +296,7 @@ def resize_figure(fig, axes, width, height, plot_params): axes.set_ylim(0, new_limits[1]) -def draw_gates(axes, qubit_lines, drawing_order, gate_grid, wire_grid, - plot_params): +def draw_gates(axes, qubit_lines, drawing_order, gate_grid, wire_grid, plot_params): """ Draws the gates. @@ -323,14 +316,17 @@ def draw_gates(axes, qubit_lines, drawing_order, gate_grid, wire_grid, (gate_str, targets, controls) = data targets_order = [drawing_order[tgt] for tgt in targets] draw_gate( - axes, gate_str, gate_grid[idx], - [wire_grid[tgt] for tgt in targets_order], targets_order, - [wire_grid[drawing_order[ctrl]] - for ctrl in controls], plot_params) + axes, + gate_str, + gate_grid[idx], + [wire_grid[tgt] for tgt in targets_order], + targets_order, + [wire_grid[drawing_order[ctrl]] for ctrl in controls], + plot_params, + ) -def draw_gate(axes, gate_str, gate_pos, target_wires, targets_order, - control_wires, plot_params): +def draw_gate(axes, gate_str, gate_pos, target_wires, targets_order, control_wires, plot_params): """ Draws a single gate at a given location. @@ -349,47 +345,56 @@ def draw_gate(axes, gate_str, gate_pos, target_wires, targets_order, """ # Special cases if gate_str == 'Z' and len(control_wires) == 1: - draw_control_z_gate(axes, gate_pos, target_wires[0], control_wires[0], - plot_params) + draw_control_z_gate(axes, gate_pos, target_wires[0], control_wires[0], plot_params) elif gate_str == 'X': draw_x_gate(axes, gate_pos, target_wires[0], plot_params) elif gate_str == 'Swap': - draw_swap_gate(axes, gate_pos, target_wires[0], target_wires[1], - plot_params) + draw_swap_gate(axes, gate_pos, target_wires[0], target_wires[1], plot_params) elif gate_str == 'Measure': draw_measure_gate(axes, gate_pos, target_wires[0], plot_params) else: if len(target_wires) == 1: - draw_generic_gate(axes, gate_pos, target_wires[0], gate_str, - plot_params) + draw_generic_gate(axes, gate_pos, target_wires[0], gate_str, plot_params) else: - if sorted(targets_order) != list( - range(min(targets_order), - max(targets_order) + 1)): + if sorted(targets_order) != list(range(min(targets_order), max(targets_order) + 1)): raise RuntimeError( 'Multi-qubit gate with non-neighbouring qubits!\n' - + 'Gate: {} on wires {}'.format(gate_str, targets_order)) - - multi_qubit_gate(axes, gate_str, gate_pos, min(target_wires), - max(target_wires), plot_params) + + 'Gate: {} on wires {}'.format(gate_str, targets_order) + ) + + multi_qubit_gate( + axes, + gate_str, + gate_pos, + min(target_wires), + max(target_wires), + plot_params, + ) if not control_wires: return for control_wire in control_wires: axes.add_patch( - Circle((gate_pos, control_wire), - plot_params['control_radius'], - ec='k', - fc='k', - fill=True, - lw=plot_params['linewidth'])) + Circle( + (gate_pos, control_wire), + plot_params['control_radius'], + ec='k', + fc='k', + fill=True, + lw=plot_params['linewidth'], + ) + ) all_wires = target_wires + control_wires axes.add_line( - Line2D((gate_pos, gate_pos), (min(all_wires), max(all_wires)), - color='k', - lw=plot_params['linewidth'])) + Line2D( + (gate_pos, gate_pos), + (min(all_wires), max(all_wires)), + color='k', + lw=plot_params['linewidth'], + ) + ) def draw_generic_gate(axes, gate_pos, wire_pos, gate_str, plot_params): @@ -414,14 +419,17 @@ def draw_generic_gate(axes, gate_pos, wire_pos, gate_str, plot_params): height = obj.get_window_extent(renderer).height * factor + 2 * gate_offset axes.add_patch( - Rectangle((gate_pos - width / 2, wire_pos - height / 2), - width, - height, - ec='k', - fc='w', - fill=True, - lw=plot_params['linewidth'], - zorder=6)) + Rectangle( + (gate_pos - width / 2, wire_pos - height / 2), + width, + height, + ec='k', + fc='w', + fill=True, + lw=plot_params['linewidth'], + zorder=6, + ) + ) def draw_measure_gate(axes, gate_pos, wire_pos, plot_params): @@ -441,38 +449,40 @@ def draw_measure_gate(axes, gate_pos, wire_pos, plot_params): y_ref = wire_pos - 0.3 * height # Cannot use PatchCollection for the arc due to bug in matplotlib code... - arc = Arc((gate_pos, y_ref), - width * 0.7, - height * 0.8, - theta1=0, - theta2=180, - ec='k', - fc='w', - zorder=5) + arc = Arc( + (gate_pos, y_ref), + width * 0.7, + height * 0.8, + theta1=0, + theta2=180, + ec='k', + fc='w', + zorder=5, + ) axes.add_patch(arc) patches = [ - Rectangle((gate_pos - width / 2, wire_pos - height / 2), - width, - height, - fill=True), - Line2D((gate_pos, gate_pos + width * 0.35), - (y_ref, wire_pos + height * 0.35), - color='k', - linewidth=1) + Rectangle((gate_pos - width / 2, wire_pos - height / 2), width, height, fill=True), + Line2D( + (gate_pos, gate_pos + width * 0.35), + (y_ref, wire_pos + height * 0.35), + color='k', + linewidth=1, + ), ] - gate = PatchCollection(patches, - edgecolors='k', - facecolors='w', - linewidths=plot_params['linewidth'], - zorder=5) + gate = PatchCollection( + patches, + edgecolors='k', + facecolors='w', + linewidths=plot_params['linewidth'], + zorder=5, + ) gate.set_label('Measure') axes.add_collection(gate) -def multi_qubit_gate(axes, gate_str, gate_pos, wire_pos_min, wire_pos_max, - plot_params): +def multi_qubit_gate(axes, gate_str, gate_pos, wire_pos_min, wire_pos_max, plot_params): """ Draws a multi-target qubit gate. @@ -486,27 +496,31 @@ def multi_qubit_gate(axes, gate_str, gate_pos, wire_pos_min, wire_pos_max, """ gate_offset = plot_params['gate_offset'] y_center = (wire_pos_max - wire_pos_min) / 2 + wire_pos_min - obj = axes.text(gate_pos, - y_center, - gate_str, - color='k', - ha='center', - va='center', - size=plot_params['fontsize'], - zorder=7) + obj = axes.text( + gate_pos, + y_center, + gate_str, + color='k', + ha='center', + va='center', + size=plot_params['fontsize'], + zorder=7, + ) height = wire_pos_max - wire_pos_min + 2 * gate_offset inv = axes.transData.inverted() - width = inv.transform_bbox( - obj.get_window_extent(obj.figure.canvas.get_renderer())).width + width = inv.transform_bbox(obj.get_window_extent(obj.figure.canvas.get_renderer())).width return axes.add_patch( - Rectangle((gate_pos - width / 2, wire_pos_min - gate_offset), - width, - height, - edgecolor='k', - facecolor='w', - fill=True, - lw=plot_params['linewidth'], - zorder=6)) + Rectangle( + (gate_pos - width / 2, wire_pos_min - gate_offset), + width, + height, + edgecolor='k', + facecolor='w', + fill=True, + lw=plot_params['linewidth'], + zorder=6, + ) + ) def draw_x_gate(axes, gate_pos, wire_pos, plot_params): @@ -521,14 +535,15 @@ def draw_x_gate(axes, gate_pos, wire_pos, plot_params): """ not_radius = plot_params['not_radius'] - gate = PatchCollection([ - Circle((gate_pos, wire_pos), not_radius, fill=False), - Line2D((gate_pos, gate_pos), - (wire_pos - not_radius, wire_pos + not_radius)) - ], - edgecolors='k', - facecolors='w', - linewidths=plot_params['linewidth']) + gate = PatchCollection( + [ + Circle((gate_pos, wire_pos), not_radius, fill=False), + Line2D((gate_pos, gate_pos), (wire_pos - not_radius, wire_pos + not_radius)), + ], + edgecolors='k', + facecolors='w', + linewidths=plot_params['linewidth'], + ) gate.set_label('NOT') axes.add_collection(gate) @@ -544,16 +559,16 @@ def draw_control_z_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): y2 (float): y coordinate of the 2nd qubit wire plot_params (dict): plot parameters """ - gate = PatchCollection([ - Circle( - (gate_pos, wire_pos1), plot_params['control_radius'], fill=True), - Circle( - (gate_pos, wire_pos2), plot_params['control_radius'], fill=True), - Line2D((gate_pos, gate_pos), (wire_pos1, wire_pos2)) - ], - edgecolors='k', - facecolors='k', - linewidths=plot_params['linewidth']) + gate = PatchCollection( + [ + Circle((gate_pos, wire_pos1), plot_params['control_radius'], fill=True), + Circle((gate_pos, wire_pos2), plot_params['control_radius'], fill=True), + Line2D((gate_pos, gate_pos), (wire_pos1, wire_pos2)), + ], + edgecolors='k', + facecolors='k', + linewidths=plot_params['linewidth'], + ) gate.set_label('CZ') axes.add_collection(gate) @@ -573,15 +588,11 @@ def draw_swap_gate(axes, gate_pos, wire_pos1, wire_pos2, plot_params): lines = [] for wire_pos in (wire_pos1, wire_pos2): - lines.append([(gate_pos - delta, wire_pos - delta), - (gate_pos + delta, wire_pos + delta)]) - lines.append([(gate_pos - delta, wire_pos + delta), - (gate_pos + delta, wire_pos - delta)]) + lines.append([(gate_pos - delta, wire_pos - delta), (gate_pos + delta, wire_pos + delta)]) + lines.append([(gate_pos - delta, wire_pos + delta), (gate_pos + delta, wire_pos - delta)]) lines.append([(gate_pos, wire_pos1), (gate_pos, wire_pos2)]) - gate = LineCollection(lines, - colors='k', - linewidths=plot_params['linewidth']) + gate = LineCollection(lines, colors='k', linewidths=plot_params['linewidth']) gate.set_label('SWAP') axes.add_collection(gate) @@ -602,11 +613,13 @@ def draw_wires(axes, n_labels, gate_grid, wire_grid, plot_params): lines = [] for i in range(n_labels): - lines.append(((gate_grid[0] - plot_params['column_spacing'], - wire_grid[i]), (gate_grid[-1], wire_grid[i]))) - all_lines = LineCollection(lines, - linewidths=plot_params['linewidth'], - edgecolor='k') + lines.append( + ( + (gate_grid[0] - plot_params['column_spacing'], wire_grid[i]), + (gate_grid[-1], wire_grid[i]), + ) + ) + all_lines = LineCollection(lines, linewidths=plot_params['linewidth'], edgecolor='k') all_lines.set_label('qubit_wires') axes.add_collection(all_lines) @@ -626,5 +639,10 @@ def draw_labels(axes, qubit_labels, drawing_order, wire_grid, plot_params): """ for qubit_id in qubit_labels: wire_idx = drawing_order[qubit_id] - text(axes, plot_params['x_offset'], wire_grid[wire_idx], - qubit_labels[qubit_id], plot_params) + text( + axes, + plot_params['x_offset'], + wire_grid[wire_idx], + qubit_labels[qubit_id], + plot_params, + ) diff --git a/projectq/backends/_circuits/_plot_test.py b/projectq/backends/_circuits/_plot_test.py index cd5d3ab0f..85a2f8d4c 100644 --- a/projectq/backends/_circuits/_plot_test.py +++ b/projectq/backends/_circuits/_plot_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -106,11 +107,9 @@ def axes(): def test_gate_width(axes, gate_str, plot_params): width = _plot.gate_width(axes, gate_str, plot_params) if gate_str == 'X': - assert width == 2 * plot_params['not_radius'] / plot_params[ - 'units_per_inch'] + assert width == 2 * plot_params['not_radius'] / plot_params['units_per_inch'] elif gate_str == 'Swap': - assert width == 2 * plot_params['swap_delta'] / plot_params[ - 'units_per_inch'] + assert width == 2 * plot_params['swap_delta'] / plot_params['units_per_inch'] elif gate_str == 'Measure': assert width == plot_params['mgate_width'] else: @@ -118,9 +117,7 @@ def test_gate_width(axes, gate_str, plot_params): def test_calculate_gate_grid(axes, plot_params): - qubit_lines = { - 0: [('X', [0], []), ('X', [0], []), ('X', [0], []), ('X', [0], [])] - } + qubit_lines = {0: [('X', [0], []), ('X', [0], []), ('X', [0], []), ('X', [0], [])]} gate_grid = _plot.calculate_gate_grid(axes, qubit_lines, plot_params) assert len(gate_grid) == 5 @@ -145,16 +142,20 @@ def test_create_figure(plot_params): def test_draw_single_gate(axes, plot_params): with pytest.raises(RuntimeError): - _plot.draw_gate(axes, 'MyGate', 2, [0, 0, 0], [0, 1, 3], [], - plot_params) + _plot.draw_gate(axes, 'MyGate', 2, [0, 0, 0], [0, 1, 3], [], plot_params) _plot.draw_gate(axes, 'MyGate', 2, [0, 0, 0], [0, 1, 2], [], plot_params) def test_draw_simple(plot_params): qubit_lines = { - 0: [('X', [0], []), ('Z', [0], []), ('Z', [0], [1]), - ('Swap', [0, 1], []), ('Measure', [0], [])], - 1: [None, None, None, None, None] + 0: [ + ('X', [0], []), + ('Z', [0], []), + ('Z', [0], [1]), + ('Swap', [0, 1], []), + ('Measure', [0], []), + ], + 1: [None, None, None, None, None], } fig, axes = _plot.to_draw(qubit_lines) @@ -176,25 +177,19 @@ def test_draw_simple(plot_params): else: text_gates.append(text) - assert all( - label.get_position()[0] == pytest.approx(plot_params['x_offset']) - for label in labels) - assert (abs(labels[1].get_position()[1] - - labels[0].get_position()[1]) == pytest.approx(wire_height)) + assert all(label.get_position()[0] == pytest.approx(plot_params['x_offset']) for label in labels) + assert abs(labels[1].get_position()[1] - labels[0].get_position()[1]) == pytest.approx(wire_height) # X gate x_gate = [obj for obj in axes.collections if obj.get_label() == 'NOT'][0] # find the filled circles - assert (x_gate.get_paths()[0].get_extents().width == pytest.approx( - 2 * not_radius)) - assert (x_gate.get_paths()[0].get_extents().height == pytest.approx( - 2 * not_radius)) + assert x_gate.get_paths()[0].get_extents().width == pytest.approx(2 * not_radius) + assert x_gate.get_paths()[0].get_extents().height == pytest.approx(2 * not_radius) # find the vertical bar x_vertical = x_gate.get_paths()[1] assert len(x_vertical) == 2 - assert x_vertical.get_extents().width == 0. - assert (x_vertical.get_extents().height == pytest.approx( - 2 * plot_params['not_radius'])) + assert x_vertical.get_extents().width == 0.0 + assert x_vertical.get_extents().height == pytest.approx(2 * plot_params['not_radius']) # Z gate assert len(text_gates) == 1 @@ -206,17 +201,15 @@ def test_draw_simple(plot_params): # find the filled circles for control in cz_gate.get_paths()[:-1]: assert control.get_extents().width == pytest.approx(2 * control_radius) - assert control.get_extents().height == pytest.approx(2 - * control_radius) + assert control.get_extents().height == pytest.approx(2 * control_radius) # find the vertical bar cz_vertical = cz_gate.get_paths()[-1] assert len(cz_vertical) == 2 - assert cz_vertical.get_extents().width == 0. - assert (cz_vertical.get_extents().height == pytest.approx(wire_height)) + assert cz_vertical.get_extents().width == 0.0 + assert cz_vertical.get_extents().height == pytest.approx(wire_height) # Swap gate - swap_gate = [obj for obj in axes.collections - if obj.get_label() == 'SWAP'][0] + swap_gate = [obj for obj in axes.collections if obj.get_label() == 'SWAP'][0] # find the filled circles for qubit in swap_gate.get_paths()[:-1]: assert qubit.get_extents().width == pytest.approx(2 * swap_delta) @@ -224,18 +217,14 @@ def test_draw_simple(plot_params): # find the vertical bar swap_vertical = swap_gate.get_paths()[-1] assert len(swap_vertical) == 2 - assert swap_vertical.get_extents().width == 0. - assert (swap_vertical.get_extents().height == pytest.approx(wire_height)) + assert swap_vertical.get_extents().width == 0.0 + assert swap_vertical.get_extents().height == pytest.approx(wire_height) # Measure gate - measure_gate = [ - obj for obj in axes.collections if obj.get_label() == 'Measure' - ][0] + measure_gate = [obj for obj in axes.collections if obj.get_label() == 'Measure'][0] - assert (measure_gate.get_paths()[0].get_extents().width == pytest.approx( - mgate_width)) - assert (measure_gate.get_paths()[0].get_extents().height == pytest.approx( - 0.9 * mgate_width)) + assert measure_gate.get_paths()[0].get_extents().width == pytest.approx(mgate_width) + assert measure_gate.get_paths()[0].get_extents().height == pytest.approx(0.9 * mgate_width) def test_draw_advanced(plot_params): @@ -257,33 +246,15 @@ def test_draw_advanced(plot_params): assert text.get_text() == r'$|0\rangle$' # NB numbering of wire starts from bottom. - _, axes = _plot.to_draw(qubit_lines, - qubit_labels={ - 0: 'qb0', - 1: 'qb1' - }, - drawing_order={ - 0: 0, - 1: 1 - }) - assert ([axes.texts[qubit_id].get_text() - for qubit_id in range(2)] == ['qb0', 'qb1']) + _, axes = _plot.to_draw(qubit_lines, qubit_labels={0: 'qb0', 1: 'qb1'}, drawing_order={0: 0, 1: 1}) + assert [axes.texts[qubit_id].get_text() for qubit_id in range(2)] == ['qb0', 'qb1'] positions = [axes.texts[qubit_id].get_position() for qubit_id in range(2)] assert positions[1][1] > positions[0][1] - _, axes = _plot.to_draw(qubit_lines, - qubit_labels={ - 0: 'qb2', - 1: 'qb3' - }, - drawing_order={ - 0: 1, - 1: 0 - }) - - assert ([axes.texts[qubit_id].get_text() - for qubit_id in range(2)] == ['qb2', 'qb3']) + _, axes = _plot.to_draw(qubit_lines, qubit_labels={0: 'qb2', 1: 'qb3'}, drawing_order={0: 1, 1: 0}) + + assert [axes.texts[qubit_id].get_text() for qubit_id in range(2)] == ['qb2', 'qb3'] positions = [axes.texts[qubit_id].get_position() for qubit_id in range(2)] assert positions[1][1] < positions[0][1] diff --git a/projectq/backends/_circuits/_to_latex.py b/projectq/backends/_circuits/_to_latex.py index 385f3d3f3..37cc17758 100755 --- a/projectq/backends/_circuits/_to_latex.py +++ b/projectq/backends/_circuits/_to_latex.py @@ -1,4 +1,5 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# -*- coding: utf-8 -*- +# Copyright 2017, 2021 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. @@ -13,8 +14,17 @@ # limitations under the License. import json -from projectq.ops import (Allocate, Deallocate, DaggeredGate, get_inverse, - Measure, SqrtSwap, Swap, X, Z) +from projectq.ops import ( + Allocate, + Deallocate, + DaggeredGate, + get_inverse, + Measure, + SqrtSwap, + Swap, + X, + Z, +) def to_latex(circuit, drawing_order=None, draw_gates_in_parallel=True): @@ -49,11 +59,6 @@ class name string as a key. Every gate can have its own width, height, pre tex_doc_str (string): Latex document string which can be compiled using, e.g., pdflatex. """ - try: - FileNotFoundError - except NameError: - FileNotFoundError = IOError # for Python2 compatibility - try: with open('settings.json') as settings_file: settings = json.load(settings_file) @@ -61,10 +66,7 @@ class name string as a key. Every gate can have its own width, height, pre settings = write_settings(get_default_settings()) text = _header(settings) - text += _body(circuit, - settings, - drawing_order, - draw_gates_in_parallel=draw_gates_in_parallel) + text += _body(circuit, settings, drawing_order, draw_gates_in_parallel=draw_gates_in_parallel) text += _footer(settings) return text @@ -90,89 +92,40 @@ def get_default_settings(): """ settings = dict() settings['gate_shadow'] = True - settings['lines'] = ({ + settings['lines'] = { 'style': 'very thin', 'double_classical': True, 'init_quantum': True, - 'double_lines_sep': .04 - }) - settings['gates'] = ({ - 'HGate': { - 'width': .5, - 'offset': .3, - 'pre_offset': .1 - }, - 'XGate': { - 'width': .35, - 'height': .35, - 'offset': .1 - }, - 'SqrtXGate': { - 'width': .7, - 'offset': .3, - 'pre_offset': .1 - }, - 'SwapGate': { - 'width': .35, - 'height': .35, - 'offset': .1 - }, - 'SqrtSwapGate': { - 'width': .35, - 'height': .35, - 'offset': .1 - }, - 'Rx': { - 'width': 1., - 'height': .8, - 'pre_offset': .2, - 'offset': .3 - }, - 'Ry': { - 'width': 1., - 'height': .8, - 'pre_offset': .2, - 'offset': .3 - }, - 'Rz': { - 'width': 1., - 'height': .8, - 'pre_offset': .2, - 'offset': .3 - }, - 'Ph': { - 'width': 1., - 'height': .8, - 'pre_offset': .2, - 'offset': .3 - }, - 'EntangleGate': { - 'width': 1.8, - 'offset': .2, - 'pre_offset': .2 - }, + 'double_lines_sep': 0.04, + } + settings['gates'] = { + 'HGate': {'width': 0.5, 'offset': 0.3, 'pre_offset': 0.1}, + 'XGate': {'width': 0.35, 'height': 0.35, 'offset': 0.1}, + 'SqrtXGate': {'width': 0.7, 'offset': 0.3, 'pre_offset': 0.1}, + 'SwapGate': {'width': 0.35, 'height': 0.35, 'offset': 0.1}, + 'SqrtSwapGate': {'width': 0.35, 'height': 0.35, 'offset': 0.1}, + 'Rx': {'width': 1.0, 'height': 0.8, 'pre_offset': 0.2, 'offset': 0.3}, + 'Ry': {'width': 1.0, 'height': 0.8, 'pre_offset': 0.2, 'offset': 0.3}, + 'Rz': {'width': 1.0, 'height': 0.8, 'pre_offset': 0.2, 'offset': 0.3}, + 'Ph': {'width': 1.0, 'height': 0.8, 'pre_offset': 0.2, 'offset': 0.3}, + 'EntangleGate': {'width': 1.8, 'offset': 0.2, 'pre_offset': 0.2}, 'DeallocateQubitGate': { - 'height': .15, - 'offset': .2, - 'width': .2, - 'pre_offset': .1 + 'height': 0.15, + 'offset': 0.2, + 'width': 0.2, + 'pre_offset': 0.1, }, 'AllocateQubitGate': { - 'height': .15, - 'width': .2, - 'offset': .1, - 'pre_offset': .1, + 'height': 0.15, + 'width': 0.2, + 'offset': 0.1, + 'pre_offset': 0.1, 'draw_id': False, - 'allocate_at_zero': False + 'allocate_at_zero': False, }, - 'MeasureGate': { - 'width': 0.75, - 'offset': .2, - 'height': .5, - 'pre_offset': .2 - } - }) - settings['control'] = {'size': .1, 'shadow': False} + 'MeasureGate': {'width': 0.75, 'offset': 0.2, 'height': 0.5, 'pre_offset': 0.2}, + } + settings['control'] = {'size': 0.1, 'shadow': False} return settings @@ -185,18 +138,21 @@ def _header(settings): Returns: header (string): Header of the Latex document. """ - packages = ("\\documentclass{standalone}\n\\usepackage[margin=1in]" - "{geometry}\n\\usepackage[hang,small,bf]{caption}\n" - "\\usepackage{tikz}\n" - "\\usepackage{braket}\n\\usetikzlibrary{backgrounds,shadows." - "blur,fit,decorations.pathreplacing,shapes}\n\n") - - init = ("\\begin{document}\n" - "\\begin{tikzpicture}[scale=0.8, transform shape]\n\n") - - gate_style = ("\\tikzstyle{basicshadow}=[blur shadow={shadow blur steps=8," - " shadow xshift=0.7pt, shadow yshift=-0.7pt, shadow scale=" - "1.02}]") + packages = ( + "\\documentclass{standalone}\n\\usepackage[margin=1in]" + "{geometry}\n\\usepackage[hang,small,bf]{caption}\n" + "\\usepackage{tikz}\n" + "\\usepackage{braket}\n\\usetikzlibrary{backgrounds,shadows." + "blur,fit,decorations.pathreplacing,shapes}\n\n" + ) + + init = "\\begin{document}\n" "\\begin{tikzpicture}[scale=0.8, transform shape]\n\n" + + gate_style = ( + "\\tikzstyle{basicshadow}=[blur shadow={shadow blur steps=8," + " shadow xshift=0.7pt, shadow yshift=-0.7pt, shadow scale=" + "1.02}]" + ) if not (settings['gate_shadow'] or settings['control']['shadow']): gate_style = "" @@ -206,33 +162,37 @@ def _header(settings): gate_style += "basicshadow" gate_style += "]\n" - gate_style += ("\\tikzstyle{operator}=[basic,minimum size=1.5em]\n" - "\\tikzstyle{phase}=[fill=black,shape=circle," + - "minimum size={}".format(settings['control']['size']) + - "cm,inner sep=0pt,outer sep=0pt,draw=black") + gate_style += ( + "\\tikzstyle{operator}=[basic,minimum size=1.5em]\n" + "\\tikzstyle{phase}=[fill=black,shape=circle," + + "minimum size={}".format(settings['control']['size']) + + "cm,inner sep=0pt,outer sep=0pt,draw=black" + ) if settings['control']['shadow']: gate_style += ",basicshadow" - gate_style += ("]\n\\tikzstyle{none}=[inner sep=0pt,outer sep=-.5pt," - "minimum height=0.5cm+1pt]\n" - "\\tikzstyle{measure}=[operator,inner sep=0pt,minimum " + - "height={}cm, minimum width={}cm]\n".format( - settings['gates']['MeasureGate']['height'], - settings['gates']['MeasureGate']['width']) + - "\\tikzstyle{xstyle}=[circle,basic,minimum height=") - x_gate_radius = min(settings['gates']['XGate']['height'], - settings['gates']['XGate']['width']) - gate_style += ("{x_rad}cm,minimum width={x_rad}cm,inner sep=-1pt," - "{linestyle}]\n").format( - x_rad=x_gate_radius, - linestyle=settings['lines']['style']) + gate_style += ( + "]\n\\tikzstyle{none}=[inner sep=0pt,outer sep=-.5pt," + "minimum height=0.5cm+1pt]\n" + "\\tikzstyle{measure}=[operator,inner sep=0pt,minimum " + + "height={}cm, minimum width={}cm]\n".format( + settings['gates']['MeasureGate']['height'], + settings['gates']['MeasureGate']['width'], + ) + + "\\tikzstyle{xstyle}=[circle,basic,minimum height=" + ) + x_gate_radius = min(settings['gates']['XGate']['height'], settings['gates']['XGate']['width']) + gate_style += ("{x_rad}cm,minimum width={x_rad}cm,inner sep=-1pt," "{linestyle}]\n").format( + x_rad=x_gate_radius, linestyle=settings['lines']['style'] + ) if settings['gate_shadow']: - gate_style += ("\\tikzset{\nshadowed/.style={preaction={transform " - "canvas={shift={(0.5pt,-0.5pt)}}, draw=gray, opacity=" - "0.4}},\n}\n") + gate_style += ( + "\\tikzset{\nshadowed/.style={preaction={transform " + "canvas={shift={(0.5pt,-0.5pt)}}, draw=gray, opacity=" + "0.4}},\n}\n" + ) gate_style += "\\tikzstyle{swapstyle}=[" gate_style += "inner sep=-1pt, outer sep=-1pt, minimum width=0pt]\n" - edge_style = ("\\tikzstyle{edgestyle}=[" + settings['lines']['style'] + - "]\n") + edge_style = "\\tikzstyle{edgestyle}=[" + settings['lines']['style'] + "]\n" return packages + init + gate_style + edge_style @@ -266,10 +226,13 @@ def _body(circuit, settings, drawing_order=None, draw_gates_in_parallel=True): for line in drawing_order: code.append( - conv.to_tikz(line, - circuit, - end=to_where, - draw_gates_in_parallel=draw_gates_in_parallel)) + conv.to_tikz( + line, + circuit, + end=to_where, + draw_gates_in_parallel=draw_gates_in_parallel, + ) + ) return "".join(code) @@ -291,6 +254,7 @@ class _Circ2Tikz(object): It uses the settings dictionary for gate offsets, sizes, spacing, ... """ + def __init__(self, settings, num_lines): """ Initialize a circuit to latex converter object. @@ -301,7 +265,7 @@ def __init__(self, settings, num_lines): circuit. """ self.settings = settings - self.pos = [0.] * num_lines + self.pos = [0.0] * num_lines self.op_count = [0] * num_lines self.is_quantum = [settings['lines']['init_quantum']] * num_lines @@ -338,29 +302,24 @@ def to_tikz(self, line, circuit, end=None, draw_gates_in_parallel=True): all_lines = lines + ctrl_lines all_lines.remove(line) # remove current line - for l in all_lines: + for ll in all_lines: gate_idx = 0 - while not (circuit[l][gate_idx] == cmds[i]): + while not (circuit[ll][gate_idx] == cmds[i]): gate_idx += 1 - tikz_code.append(self.to_tikz(l, circuit, gate_idx)) + tikz_code.append(self.to_tikz(ll, circuit, gate_idx)) # we are taking care of gate 0 (the current one) - circuit[l] = circuit[l][1:] + circuit[ll] = circuit[ll][1:] all_lines = lines + ctrl_lines - pos = max([ - self.pos[l] for l in range(min(all_lines), - max(all_lines) + 1) - ]) - for l in range(min(all_lines), max(all_lines) + 1): - self.pos[l] = pos + self._gate_pre_offset(gate) + pos = max([self.pos[ll] for ll in range(min(all_lines), max(all_lines) + 1)]) + for ll in range(min(all_lines), max(all_lines) + 1): + self.pos[ll] = pos + self._gate_pre_offset(gate) connections = "" - for l in all_lines: - connections += self._line(self.op_count[l] - 1, - self.op_count[l], - line=l) + for ll in all_lines: + connections += self._line(self.op_count[ll] - 1, self.op_count[ll], line=ll) add_str = "" if gate == X: # draw NOT-gate with controls @@ -374,39 +333,39 @@ def to_tikz(self, line, circuit, end=None, draw_gates_in_parallel=True): elif gate == Swap: add_str = self._swap_gate(lines, ctrl_lines) elif gate == SqrtSwap: - add_str = self._sqrtswap_gate(lines, - ctrl_lines, - daggered=False) + add_str = self._sqrtswap_gate(lines, ctrl_lines, daggered=False) elif gate == get_inverse(SqrtSwap): add_str = self._sqrtswap_gate(lines, ctrl_lines, daggered=True) elif gate == Measure: # draw measurement gate - for l in lines: - op = self._op(l) + for ll in lines: + op = self._op(ll) width = self._gate_width(Measure) height = self._gate_height(Measure) - shift0 = .07 * height - shift1 = .36 * height - shift2 = .1 * width - add_str += ("\n\\node[measure,edgestyle] ({op}) at ({pos}" - ",-{line}) {{}};\n\\draw[edgestyle] ([yshift=" - "-{shift1}cm,xshift={shift2}cm]{op}.west) to " - "[out=60,in=180] ([yshift={shift0}cm]{op}." - "center) to [out=0, in=120] ([yshift=-{shift1}" - "cm,xshift=-{shift2}cm]{op}.east);\n" - "\\draw[edgestyle] ([yshift=-{shift1}cm]{op}." - "center) to ([yshift=-{shift2}cm,xshift=-" - "{shift1}cm]{op}.north east);").format( - op=op, - pos=self.pos[l], - line=l, - shift0=shift0, - shift1=shift1, - shift2=shift2) - self.op_count[l] += 1 - self.pos[l] += (self._gate_width(gate) + - self._gate_offset(gate)) - self.is_quantum[l] = False + shift0 = 0.07 * height + shift1 = 0.36 * height + shift2 = 0.1 * width + add_str += ( + "\n\\node[measure,edgestyle] ({op}) at ({pos}" + ",-{line}) {{}};\n\\draw[edgestyle] ([yshift=" + "-{shift1}cm,xshift={shift2}cm]{op}.west) to " + "[out=60,in=180] ([yshift={shift0}cm]{op}." + "center) to [out=0, in=120] ([yshift=-{shift1}" + "cm,xshift=-{shift2}cm]{op}.east);\n" + "\\draw[edgestyle] ([yshift=-{shift1}cm]{op}." + "center) to ([yshift=-{shift2}cm,xshift=-" + "{shift1}cm]{op}.north east);" + ).format( + op=op, + pos=self.pos[ll], + line=ll, + shift0=shift0, + shift1=shift1, + shift2=shift2, + ) + self.op_count[ll] += 1 + self.pos[ll] += self._gate_width(gate) + self._gate_offset(gate) + self.is_quantum[ll] = False elif gate == Allocate: # draw 'begin line' add_str = "\n\\node[none] ({}) at ({},-{}) {{$\\Ket{{0}}{}$}};" @@ -415,15 +374,15 @@ def to_tikz(self, line, circuit, end=None, draw_gates_in_parallel=True): id_str = "^{{\\textcolor{{red}}{{{}}}}}".format(cmds[i].id) xpos = self.pos[line] try: - if (self.settings['gates']['AllocateQubitGate'] - ['allocate_at_zero']): + if self.settings['gates']['AllocateQubitGate']['allocate_at_zero']: self.pos[line] -= self._gate_pre_offset(gate) xpos = self._gate_pre_offset(gate) except KeyError: pass self.pos[line] = max( xpos + self._gate_offset(gate) + self._gate_width(gate), - self.pos[line]) + self.pos[line], + ) add_str = add_str.format(self._op(line), xpos, line, id_str) self.op_count[line] += 1 self.is_quantum[line] = self.settings['lines']['init_quantum'] @@ -434,27 +393,25 @@ def to_tikz(self, line, circuit, end=None, draw_gates_in_parallel=True): add_str = add_str.format(op, self.pos[line], line) yshift = str(self._gate_height(gate)) + "cm]" add_str += ( - "\n\\draw ([yshift={yshift}{op}.center) edge " - "[edgestyle] ([yshift=-{yshift}{op}.center);").format( - op=op, yshift=yshift) + "\n\\draw ([yshift={yshift}{op}.center) edge [edgestyle] ([yshift=-{yshift}{op}.center);" + ).format(op=op, yshift=yshift) self.op_count[line] += 1 - self.pos[line] += (self._gate_width(gate) + - self._gate_offset(gate)) + self.pos[line] += self._gate_width(gate) + self._gate_offset(gate) else: # regular gate must draw the lines it does not act upon # if it spans multiple qubits add_str = self._regular_gate(gate, lines, ctrl_lines) - for l in lines: - self.is_quantum[l] = True + for ll in lines: + self.is_quantum[ll] = True tikz_code.append(add_str) if not gate == Allocate: tikz_code.append(connections) if not draw_gates_in_parallel: - for l in range(len(self.pos)): - if l != line: - self.pos[l] = self.pos[line] + for ll in range(len(self.pos)): + if ll != line: + self.pos[ll] = self.pos[line] circuit[line] = circuit[line][end:] return "".join(tikz_code) @@ -488,7 +445,7 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): ctrl_lines (list): List of qubit lines which act as controls. daggered (bool): Show the daggered one if True. """ - assert (len(lines) == 2) # sqrt swap gate acts on 2 qubits + assert len(lines) == 2 # sqrt swap gate acts on 2 qubits delta_pos = self._gate_offset(SqrtSwap) gate_width = self._gate_width(SqrtSwap) lines.sort() @@ -496,7 +453,7 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): gate_str = "" for line in lines: op = self._op(line) - w = "{}cm".format(.5 * gate_width) + w = "{}cm".format(0.5 * gate_width) s1 = "[xshift=-{w},yshift=-{w}]{op}.center".format(w=w, op=op) s2 = "[xshift={w},yshift={w}]{op}.center".format(w=w, op=op) s3 = "[xshift=-{w},yshift={w}]{op}.center".format(w=w, op=op) @@ -504,35 +461,38 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): swap_style = "swapstyle,edgestyle" if self.settings['gate_shadow']: swap_style += ",shadowed" - gate_str += ("\n\\node[swapstyle] ({op}) at ({pos},-{line}) {{}};" - "\n\\draw[{swap_style}] ({s1})--({s2});\n" - "\\draw[{swap_style}] ({s3})--({s4});").format( - op=op, - s1=s1, - s2=s2, - s3=s3, - s4=s4, - line=line, - pos=self.pos[line], - swap_style=swap_style) + gate_str += ( + "\n\\node[swapstyle] ({op}) at ({pos},-{line}) {{}};" + "\n\\draw[{swap_style}] ({s1})--({s2});\n" + "\\draw[{swap_style}] ({s3})--({s4});" + ).format( + op=op, + s1=s1, + s2=s2, + s3=s3, + s4=s4, + line=line, + pos=self.pos[line], + swap_style=swap_style, + ) # add a circled 1/2 - midpoint = (lines[0] + lines[1]) / 2. + midpoint = (lines[0] + lines[1]) / 2.0 pos = self.pos[lines[0]] - op_mid = "line{}_gate{}".format('{}-{}'.format(*lines), - self.op_count[lines[0]]) - gate_str += ("\n\\node[xstyle] ({op}) at ({pos},-{line})\ - {{\\scriptsize $\\frac{{1}}{{2}}{dagger}$}};").format( + op_mid = "line{}_gate{}".format('{}-{}'.format(*lines), self.op_count[lines[0]]) + gate_str += ( + "\n\\node[xstyle] ({op}) at ({pos},-{line})\ + {{\\scriptsize $\\frac{{1}}{{2}}{dagger}$}};" + ).format( op=op_mid, line=midpoint, pos=pos, - dagger='^{{\\dagger}}' if daggered else '') + dagger='^{{\\dagger}}' if daggered else '', + ) # add two vertical lines to connect circled 1/2 - gate_str += "\n\\draw ({}) edge[edgestyle] ({});".format( - self._op(lines[0]), op_mid) - gate_str += "\n\\draw ({}) edge[edgestyle] ({});".format( - op_mid, self._op(lines[1])) + gate_str += "\n\\draw ({}) edge[edgestyle] ({});".format(self._op(lines[0]), op_mid) + gate_str += "\n\\draw ({}) edge[edgestyle] ({});".format(op_mid, self._op(lines[1])) if len(ctrl_lines) > 0: for ctrl in ctrl_lines: @@ -561,7 +521,7 @@ def _swap_gate(self, lines, ctrl_lines): ctrl_lines (list): List of qubit lines which act as controls. """ - assert (len(lines) == 2) # swap gate acts on 2 qubits + assert len(lines) == 2 # swap gate acts on 2 qubits delta_pos = self._gate_offset(Swap) gate_width = self._gate_width(Swap) lines.sort() @@ -569,7 +529,7 @@ def _swap_gate(self, lines, ctrl_lines): gate_str = "" for line in lines: op = self._op(line) - w = "{}cm".format(.5 * gate_width) + w = "{}cm".format(0.5 * gate_width) s1 = "[xshift=-{w},yshift=-{w}]{op}.center".format(w=w, op=op) s2 = "[xshift={w},yshift={w}]{op}.center".format(w=w, op=op) s3 = "[xshift=-{w},yshift={w}]{op}.center".format(w=w, op=op) @@ -577,17 +537,20 @@ def _swap_gate(self, lines, ctrl_lines): swap_style = "swapstyle,edgestyle" if self.settings['gate_shadow']: swap_style += ",shadowed" - gate_str += ("\n\\node[swapstyle] ({op}) at ({pos},-{line}) {{}};" - "\n\\draw[{swap_style}] ({s1})--({s2});\n" - "\\draw[{swap_style}] ({s3})--({s4});").format( - op=op, - s1=s1, - s2=s2, - s3=s3, - s4=s4, - line=line, - pos=self.pos[line], - swap_style=swap_style) + gate_str += ( + "\n\\node[swapstyle] ({op}) at ({pos},-{line}) {{}};" + "\n\\draw[{swap_style}] ({s1})--({s2});\n" + "\\draw[{swap_style}] ({s3})--({s4});" + ).format( + op=op, + s1=s1, + s2=s2, + s3=s3, + s4=s4, + line=line, + pos=self.pos[line], + swap_style=swap_style, + ) gate_str += self._line(lines[0], lines[1]) if len(ctrl_lines) > 0: @@ -617,15 +580,16 @@ def _x_gate(self, lines, ctrl_lines): ctrl_lines (list): List of qubit lines which act as controls. """ - assert (len(lines) == 1) # NOT gate only acts on 1 qubit + assert len(lines) == 1 # NOT gate only acts on 1 qubit line = lines[0] delta_pos = self._gate_offset(X) gate_width = self._gate_width(X) op = self._op(line) - gate_str = ("\n\\node[xstyle] ({op}) at ({pos},-{line}) {{}};\n\\draw" - "[edgestyle] ({op}.north)--({op}.south);\n\\draw" - "[edgestyle] ({op}.west)--({op}.east);").format( - op=op, line=line, pos=self.pos[line]) + gate_str = ( + "\n\\node[xstyle] ({op}) at ({pos},-{line}) {{}};\n\\draw" + "[edgestyle] ({op}.north)--({op}.south);\n\\draw" + "[edgestyle] ({op}.west)--({op}.east);" + ).format(op=op, line=line, pos=self.pos[line]) if len(ctrl_lines) > 0: for ctrl in ctrl_lines: @@ -678,7 +642,7 @@ def _gate_width(self, gate): gates = self.settings['gates'] gate_width = gates[gate.__class__.__name__]['width'] except KeyError: - gate_width = .5 + gate_width = 0.5 return gate_width def _gate_pre_offset(self, gate): @@ -713,7 +677,7 @@ def _gate_offset(self, gate): gates = self.settings['gates'] delta_pos = gates[gate.__class__.__name__]['offset'] except KeyError: - delta_pos = .2 + delta_pos = 0.2 return delta_pos def _gate_height(self, gate): @@ -729,7 +693,7 @@ def _gate_height(self, gate): try: height = self.settings['gates'][gate.__class__.__name__]['height'] except KeyError: - height = .5 + height = 0.5 return height def _phase(self, line, pos): @@ -798,21 +762,12 @@ def _line(self, p1, p2, double=False, line=None): else: if p2 > p1: loc1, loc2 = loc2, loc1 - edge_str = ("\n\\draw ([{shift}]{op1}.{loc1}) edge[edgestyle] " - "([{shift}]{op2}.{loc2});") + edge_str = "\n\\draw ([{shift}]{op1}.{loc1}) edge[edgestyle] ([{shift}]{op2}.{loc2});" line_sep = self.settings['lines']['double_lines_sep'] - shift1 = shift.format(line_sep / 2.) - shift2 = shift.format(-line_sep / 2.) - edges_str = edge_str.format(shift=shift1, - op1=op1, - op2=op2, - loc1=loc1, - loc2=loc2) - edges_str += edge_str.format(shift=shift2, - op1=op1, - op2=op2, - loc1=loc1, - loc2=loc2) + shift1 = shift.format(line_sep / 2.0) + shift2 = shift.format(-line_sep / 2.0) + edges_str = edge_str.format(shift=shift1, op1=op1, op2=op2, loc1=loc1, loc2=loc2) + edges_str += edge_str.format(shift=shift2, op1=op1, op2=op2, loc1=loc1, loc2=loc2) return edges_str def _regular_gate(self, gate, lines, ctrl_lines): @@ -845,35 +800,35 @@ def _regular_gate(self, gate, lines, ctrl_lines): pos = self.pos[lines[0]] node_str = "\n\\node[none] ({}) at ({},-{}) {{}};" - for l in lines: - node1 = node_str.format(self._op(l), pos, l) - node2 = ("\n\\node[none,minimum height={}cm,outer sep=0] ({}) at" - " ({},-{}) {{}};").format(gate_height, - self._op(l, offset=1), - pos + gate_width / 2., l) - node3 = node_str.format(self._op(l, offset=2), pos + gate_width, l) + for line in lines: + node1 = node_str.format(self._op(line), pos, line) + node2 = ("\n\\node[none,minimum height={}cm,outer sep=0] ({}) at ({},-{}) {{}};").format( + gate_height, self._op(line, offset=1), pos + gate_width / 2.0, line + ) + node3 = node_str.format(self._op(line, offset=2), pos + gate_width, line) tex_str += node1 + node2 + node3 - if l not in gate_lines: - tex_str += self._line(self.op_count[l] - 1, - self.op_count[l], - line=l) - - tex_str += ("\n\\draw[operator,edgestyle,outer sep={width}cm] ([" - "yshift={half_height}cm]{op1}) rectangle ([yshift=-" - "{half_height}cm]{op2}) node[pos=.5] {{{name}}};").format( - width=gate_width, - op1=self._op(imin), - op2=self._op(imax, offset=2), - half_height=.5 * gate_height, - name=name) - - for l in lines: - self.pos[l] = pos + gate_width / 2. - self.op_count[l] += 1 + if line not in gate_lines: + tex_str += self._line(self.op_count[line] - 1, self.op_count[line], line=line) + + tex_str += ( + "\n\\draw[operator,edgestyle,outer sep={width}cm] ([" + "yshift={half_height}cm]{op1}) rectangle ([yshift=-" + "{half_height}cm]{op2}) node[pos=.5] {{{name}}};" + ).format( + width=gate_width, + op1=self._op(imin), + op2=self._op(imax, offset=2), + half_height=0.5 * gate_height, + name=name, + ) + + for line in lines: + self.pos[line] = pos + gate_width / 2.0 + self.op_count[line] += 1 for ctrl in ctrl_lines: if ctrl not in lines: - tex_str += self._phase(ctrl, pos + gate_width / 2.) + tex_str += self._phase(ctrl, pos + gate_width / 2.0) connect_to = imax if abs(connect_to - ctrl) > abs(imin - ctrl): connect_to = imin @@ -881,9 +836,9 @@ def _regular_gate(self, gate, lines, ctrl_lines): self.pos[ctrl] = pos + delta_pos + gate_width self.op_count[ctrl] += 1 - for l in lines: - self.op_count[l] += 2 + for line in lines: + self.op_count[line] += 2 - for l in range(min(ctrl_lines + lines), max(ctrl_lines + lines) + 1): - self.pos[l] = pos + delta_pos + gate_width + for line in range(min(ctrl_lines + lines), max(ctrl_lines + lines) + 1): + self.pos[line] = pos + delta_pos + gate_width return tex_str diff --git a/projectq/backends/_circuits/_to_latex_test.py b/projectq/backends/_circuits/_to_latex_test.py index c993bcf2e..c6032109a 100755 --- a/projectq/backends/_circuits/_to_latex_test.py +++ b/projectq/backends/_circuits/_to_latex_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,12 +16,9 @@ Tests for projectq.backends._circuits._to_latex.py. """ -import pytest -import builtins import copy from projectq import MainEngine -from projectq.cengines import LastEngineException from projectq.ops import ( BasicGate, H, @@ -35,7 +33,6 @@ get_inverse, ) from projectq.meta import Control -from projectq.backends import CircuitDrawer import projectq.backends._circuits._to_latex as _to_latex import projectq.backends._circuits._drawer as _drawer @@ -70,23 +67,12 @@ def test_default_settings(): def test_header(): settings = { 'gate_shadow': False, - 'control': { - 'shadow': False, - 'size': 0 - }, + 'control': {'shadow': False, 'size': 0}, 'gates': { - 'MeasureGate': { - 'height': 0, - 'width': 0 - }, - 'XGate': { - 'height': 1, - 'width': .5 - } + 'MeasureGate': {'height': 0, 'width': 0}, + 'XGate': {'height': 1, 'width': 0.5}, }, - 'lines': { - 'style': 'my_style' - } + 'lines': {'style': 'my_style'}, } header = _to_latex._header(settings) @@ -196,6 +182,7 @@ def test_body(): assert code.count("{{{}}}".format(str(Z))) == 1 # 1 Z gate assert code.count("{red}") == 3 + def test_body_with_drawing_order_and_gates_parallel(): drawer = _drawer.CircuitDrawer() eng = MainEngine(drawer, []) @@ -211,11 +198,8 @@ def test_body_with_drawing_order_and_gates_parallel(): H | qubit3 CNOT | (qubit1, qubit3) - # replicates the above order - order = [0, 1, 2, # initializations - 0, 1, 2, # H1, H3, H2 - 0 # CNOT - ] + # replicates the above order: first the 3 allocations, then the 3 Hadamard and 1 CNOT gates + order = [0, 1, 2, 0, 1, 2, 0] del qubit1 eng.flush() @@ -225,9 +209,7 @@ def test_body_with_drawing_order_and_gates_parallel(): settings = _to_latex.get_default_settings() settings['gates']['AllocateQubitGate']['draw_id'] = True - code = _to_latex._body(circuit_lines, settings, - drawing_order=order, - draw_gates_in_parallel=True) + code = _to_latex._body(circuit_lines, settings, drawing_order=order, draw_gates_in_parallel=True) # there are three Hadamards in parallel assert code.count("node[pos=.5] {H}") == 3 @@ -259,11 +241,8 @@ def test_body_with_drawing_order_and_gates_not_parallel(): H | qubit3 CNOT | (qubit1, qubit3) - # replicates the above order - order = [0, 1, 2, # initializations - 0, 1, 2, # H1, H3, H2 - 0 # CNOT - ] + # replicates the above order: first the 3 allocations, then the 3 Hadamard and 1 CNOT gates + order = [0, 1, 2, 0, 1, 2, 0] del qubit1 eng.flush() @@ -273,15 +252,14 @@ def test_body_with_drawing_order_and_gates_not_parallel(): settings = _to_latex.get_default_settings() settings['gates']['AllocateQubitGate']['draw_id'] = True - code = _to_latex._body(circuit_lines, settings, - drawing_order=order, - draw_gates_in_parallel=False) + code = _to_latex._body(circuit_lines, settings, drawing_order=order, draw_gates_in_parallel=False) # and the CNOT is at position 4.0, because of the offsets # which are 0.5 * 3 * 2 (due to three Hadamards) + the initialisations assert code.count("node[phase] (line0_gate4) at (4.0,-0)") == 1 assert code.count("node[xstyle] (line2_gate4) at (4.0,-2)") == 1 + def test_body_without_drawing_order_and_gates_not_parallel(): drawer = _drawer.CircuitDrawer() eng = MainEngine(drawer, []) @@ -297,12 +275,6 @@ def test_body_without_drawing_order_and_gates_not_parallel(): H | qubit3 CNOT | (qubit1, qubit3) - # replicates the above order - order = [0, 1, 2, # initializations - 0, 1, 2, # H1, H3, H2 - 0 # CNOT - ] - del qubit1 eng.flush() @@ -311,8 +283,7 @@ def test_body_without_drawing_order_and_gates_not_parallel(): settings = _to_latex.get_default_settings() settings['gates']['AllocateQubitGate']['draw_id'] = True - code = _to_latex._body(circuit_lines, settings, - draw_gates_in_parallel=False) + code = _to_latex._body(circuit_lines, settings, draw_gates_in_parallel=False) # line1_gate1 is after the cnot line2_gate_4 idx1 = code.find("node[xstyle] (line2_gate4)") diff --git a/projectq/backends/_ibm/__init__.py b/projectq/backends/_ibm/__init__.py index 289b40833..216a0e418 100755 --- a/projectq/backends/_ibm/__init__.py +++ b/projectq/backends/_ibm/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index 6486ab4d0..baa4de60c 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,30 +12,24 @@ # 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. - """ Back-end to run quantum program on IBM's Quantum Experience.""" import math import random -import json from projectq.cengines import BasicEngine from projectq.meta import get_control_count, LogicalQubitIDTag -from projectq.ops import (NOT, - Y, - Z, - T, - Tdag, - S, - Sdag, - H, - Rx, - Ry, - Rz, - Measure, - Allocate, - Deallocate, - Barrier, - FlushGate) +from projectq.ops import ( + NOT, + H, + Rx, + Ry, + Rz, + Measure, + Allocate, + Deallocate, + Barrier, + FlushGate, +) from ._ibm_http_client import send, retrieve @@ -44,10 +39,18 @@ class IBMBackend(BasicEngine): 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, - token='', device='ibmq_essex', - num_retries=3000, interval=1, - retrieve_execution=None): + + def __init__( + self, + use_hardware=False, + num_runs=1024, + verbose=False, + token='', + device='ibmq_essex', + num_retries=3000, + interval=1, + retrieve_execution=None, + ): """ Initialize the Backend object. @@ -77,12 +80,12 @@ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, self.device = 'ibmq_qasm_simulator' self._num_runs = num_runs self._verbose = verbose - self._token=token + self._token = token self._num_retries = num_retries self._interval = interval self._probabilities = dict() self.qasm = "" - self._json=[] + self._json = [] self._measured_ids = [] self._allocated_qubits = set() self._retrieve_execution = retrieve_execution @@ -110,12 +113,12 @@ def is_available(self, cmd): 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 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). """ + """Reset all temporary variables (after flush gate).""" self._clear = True self._measured_ids = [] @@ -132,7 +135,7 @@ def _store(self, cmd): self._probabilities = dict() self._clear = False self.qasm = "" - self._json=[] + self._json = [] self._allocated_qubits = set() gate = cmd.gate @@ -145,7 +148,6 @@ def _store(self, cmd): if gate == Measure: assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 - qb_id = cmd.qubits[0][0].id logical_id = None for t in cmd.tags: if isinstance(t, LogicalQubitIDTag): @@ -157,7 +159,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'}) + 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 " @@ -169,22 +171,23 @@ def _store(self, cmd): 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({})'} - 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]} + u_strs = {'Rx': 'u3({}, -pi/2, pi/2)', 'Ry': 'u3({}, 0, 0)', 'Rz': 'u1({})'} + 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]] + 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}) + 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]}) + 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.') @@ -199,10 +202,11 @@ def _logical_to_physical(self, qb_id): assert self.main_engine.mapper is not None mapping = self.main_engine.mapper.current_mapping if qb_id not in mapping: - raise RuntimeError("Unknown qubit id {}. Please make sure " - "eng.flush() was called and that the qubit " - "was eliminated during optimization." - .format(qb_id)) + raise RuntimeError( + "Unknown qubit id {}. Please make sure " + "eng.flush() was called and that the qubit " + "was eliminated during optimization.".format(qb_id) + ) return mapping[qb_id] def get_probabilities(self, qureg): @@ -257,48 +261,49 @@ def _run(self): # 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]}) + 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 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) info = {} - info['json']=self._json - info['nq']=max_qubit_id + info['json'] = self._json + info['nq'] = max_qubit_id info['shots'] = self._num_runs info['maxCredits'] = 10 info['backend'] = {'name': self.device} try: if self._retrieve_execution is None: - res = send(info, device=self.device, - token=self._token, - num_retries=self._num_retries, - interval=self._interval, - verbose=self._verbose) + res = send( + info, + device=self.device, + token=self._token, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) else: - res = retrieve(device=self.device, - token=self._token, - jobid=self._retrieve_execution, - num_retries=self._num_retries, - interval=self._interval, - verbose=self._verbose) + res = retrieve( + device=self.device, + token=self._token, + jobid=self._retrieve_execution, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) counts = res['data']['counts'] # Determine random outcome P = random.random() - p_sum = 0. + p_sum = 0.0 measured = "" - length=len(self._measured_ids) for state in counts: - probability = counts[state] * 1. / self._num_runs - 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] + probability = counts[state] * 1.0 / self._num_runs + 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 == "": @@ -306,10 +311,9 @@ def _run(self): star = "*" self._probabilities[state] = probability if self._verbose and probability > 0: - print(str(state) + " with p = " + str(probability) + - star) + print(str(state) + " with p = " + str(probability) + star) - class QB(): + class QB: def __init__(self, ID): self.id = ID diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index b2f5c898d..83d8ec58d 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,8 +26,7 @@ from requests.compat import urljoin from requests import Session -_AUTH_API_URL = ('https://auth.quantum-computing.ibm.com/api/users/' - 'loginWithToken') +_AUTH_API_URL = 'https://auth.quantum-computing.ibm.com/api/users/loginWithToken' _API_URL = 'https://api.quantum-computing.ibm.com/api/' # TODO: call to get the API version automatically @@ -37,6 +37,7 @@ 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() @@ -56,8 +57,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)} - request = super(IBMQ, self).get(urljoin(_API_URL, list_device_url), - **argument) + request = super(IBMQ, self).get(urljoin(_API_URL, list_device_url), **argument) request.raise_for_status() r_json = request.json() self.backends = dict() @@ -65,7 +65,7 @@ def get_list_devices(self, verbose=False): self.backends[obj['backend_name']] = { 'nq': obj['n_qubits'], 'coupling_map': obj['coupling_map'], - 'version': obj['backend_version'] + 'version': obj['backend_version'], } if verbose: @@ -116,10 +116,8 @@ def _authenticate(self, token=None): self.headers.update({'X-Qx-Client-Application': CLIENT_APPLICATION}) args = { 'data': None, - 'json': { - 'apiToken': token - }, - 'timeout': (self.timeout, None) + 'json': {'apiToken': token}, + 'timeout': (self.timeout, None), } request = super(IBMQ, self).post(_AUTH_API_URL, **args) request.raise_for_status() @@ -149,17 +147,16 @@ def _run(self, info, device): json_step1 = { 'data': None, 'json': { - 'backend': { - 'name': device - }, + 'backend': {'name': device}, 'allowObjectStorage': True, - 'shareLevel': 'none' + 'shareLevel': 'none', }, - 'timeout': (self.timeout, None) + 'timeout': (self.timeout, None), } request = super(IBMQ, self).post( urljoin(_API_URL, 'Network/ibm-q/Groups/open/Projects/main/Jobs'), - **json_step1) + **json_step1, + ) request.raise_for_status() r_json = request.json() upload_url = r_json['objectStorageInfo']['uploadUrl'] @@ -178,61 +175,45 @@ def _run(self, info, device): instruction_str = str(instructions).replace('\'', '\"') data = '{"qobj_id": "' + str(uuid.uuid4()) + '", ' data += '"header": {"backend_name": "' + device + '", ' - data += ('"backend_version": "' + self.backends[device]['version'] - + '"}, ') + data += '"backend_version": "' + self.backends[device]['version'] + '"}, ' data += '"config": {"shots": ' + str(info['shots']) + ', ' data += '"max_credits": ' + str(maxcredit) + ', "memory": false, ' - data += ('"parameter_binds": [], "memory_slots": ' - + str(n_classical_reg)) - data += (', "n_qubits": ' + str(n_qubits) - + '}, "schema_version": "1.2.0", ') + data += '"parameter_binds": [], "memory_slots": ' + str(n_classical_reg) + data += ', "n_qubits": ' + str(n_qubits) + '}, "schema_version": "1.2.0", ' data += '"type": "QASM", "experiments": [{"config": ' data += '{"n_qubits": ' + str(n_qubits) + ', ' data += '"memory_slots": ' + str(n_classical_reg) + '}, ' - data += ('"header": {"qubit_labels": ' - + str(q_label).replace('\'', '\"') + ', ') + data += '"header": {"qubit_labels": ' + str(q_label).replace('\'', '\"') + ', ' data += '"n_qubits": ' + str(n_classical_reg) + ', ' data += '"qreg_sizes": [["q", ' + str(n_qubits) + ']], ' data += '"clbit_labels": ' + str(c_label).replace('\'', '\"') + ', ' data += '"memory_slots": ' + str(n_classical_reg) + ', ' data += '"creg_sizes": [["c", ' + str(n_classical_reg) + ']], ' - data += ('"name": "circuit0", "global_phase": 0}, "instructions": ' + instruction_str - + '}]}') + data += '"name": "circuit0", "global_phase": 0}, "instructions": ' + instruction_str + '}]}' json_step2 = { 'data': data, - 'params': { - 'access_token': None - }, - 'timeout': (5.0, None) + 'params': {'access_token': None}, + 'timeout': (5.0, None), } request = super(IBMQ, self).put(upload_url, **json_step2) request.raise_for_status() # STEP3: CONFIRM UPLOAD - json_step3 = { - 'data': None, - 'json': None, - 'timeout': (self.timeout, None) - } - - upload_data_url = urljoin(_API_URL, - 'Network/ibm-q/Groups/open/Projects/main/Jobs/'+str(execution_id) - +'/jobDataUploaded') + json_step3 = {'data': None, 'json': None, 'timeout': (self.timeout, None)} + + upload_data_url = urljoin( + _API_URL, + 'Network/ibm-q/Groups/open/Projects/main/Jobs/' + str(execution_id) + '/jobDataUploaded', + ) request = super(IBMQ, self).post(upload_data_url, **json_step3) request.raise_for_status() return execution_id - def _get_result(self, - device, - execution_id, - num_retries=3000, - interval=1, - verbose=False): + 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) + job_status_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/' + execution_id if verbose: print("Waiting for results. [Job ID: {}]".format(execution_id)) @@ -240,22 +221,15 @@ def _get_result(self, original_sigint_handler = signal.getsignal(signal.SIGINT) def _handle_sigint_during_get_result(*_): # pragma: no cover - 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): # STEP5: WAIT FOR THE JOB TO BE RUN - json_step5 = { - 'allow_redirects': True, - 'timeout': (self.timeout, None) - } - request = super(IBMQ, - self).get(urljoin(_API_URL, job_status_url), - **json_step5) + json_step5 = {'allow_redirects': True, 'timeout': (self.timeout, None)} + request = super(IBMQ, self).get(urljoin(_API_URL, job_status_url), **json_step5) request.raise_for_status() r_json = request.json() acceptable_status = ['VALIDATING', 'VALIDATED', 'RUNNING'] @@ -263,61 +237,51 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover # STEP6: Get the endpoint to get the result json_step6 = { 'allow_redirects': True, - 'timeout': (self.timeout, None) + 'timeout': (self.timeout, None), } request = super(IBMQ, self).get( - urljoin(_API_URL, - job_status_url + '/resultDownloadUrl'), - **json_step6) + urljoin(_API_URL, job_status_url + '/resultDownloadUrl'), + **json_step6, + ) request.raise_for_status() r_json = request.json() # STEP7: Get the result json_step7 = { 'allow_redirects': True, - 'params': { - 'access_token': None - }, - 'timeout': (self.timeout, None) + 'params': {'access_token': None}, + 'timeout': (self.timeout, None), } - request = super(IBMQ, self).get(r_json['url'], - **json_step7) + request = super(IBMQ, self).get(r_json['url'], **json_step7) r_json = request.json() result = r_json['results'][0] # STEP8: Confirm the data was downloaded - json_step8 = { - 'data': None, - 'json': None, - 'timeout': (5.0, None) - } + json_step8 = {'data': None, 'json': None, 'timeout': (5.0, None)} request = super(IBMQ, self).post( - urljoin(_API_URL, - job_status_url + '/resultDownloaded'), - **json_step8) + urljoin(_API_URL, job_status_url + '/resultDownloaded'), + **json_step8, + ) r_json = request.json() return result # Note: if stays stuck if 'Validating' mode, then sthg went # wrong in step 3 if r_json['status'] not in acceptable_status: - raise Exception( - "Error while running the code. Last status: {}.". - format(r_json['status'])) + raise Exception("Error while running the code. Last status: {}.".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)) + "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): @@ -345,12 +309,7 @@ def show_devices(token=None, verbose=False): 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. @@ -365,21 +324,19 @@ def retrieve(device, 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. @@ -411,28 +368,32 @@ def send(info, 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.") + 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)) + ( + "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 460ccdf8f..278017c4e 100755 --- a/projectq/backends/_ibm/_ibm_http_client_test.py +++ b/projectq/backends/_ibm/_ibm_http_client_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +14,6 @@ # limitations under the License. """Tests for projectq.backends._ibm_http_client._ibm.py.""" -import json import pytest import requests from requests.compat import urljoin @@ -33,24 +33,16 @@ def no_requests(monkeypatch): def test_send_real_device_online_verbose(monkeypatch): json_qasm = { - 'qasms': [{ - 'qasm': 'my qasm' - }], + 'qasms': [{'qasm': 'my qasm'}], 'shots': 1, 'json': 'instructions', 'maxCredits': 10, - 'nq': 1 + 'nq': 1, } - 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.dumps(json_qasm)]) - json_data = ''.join(['{', json_body, '}']) shots = 1 - device = "ibmqx4" execution_id = '3' result_ready = [False] result = "my_result" @@ -74,56 +66,82 @@ def json(self): def raise_for_status(self): pass + # 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] == 6)): + if args[1] == urljoin(_API_URL, status_url) and (request_num[0] == 1 or request_num[0] == 6): 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) - #STEP2 - elif (args[1] == "/"+execution_id+"/jobUploadUrl" - and request_num[0] == 3): + 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, + ) + # STEP2 + elif args[1] == "/" + execution_id + "/jobUploadUrl" and request_num[0] == 3: request_num[0] += 1 return MockResponse({"url": "s3_url"}, 200) - #STEP5 - elif (args[1] == urljoin( + # STEP5 + 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] == 5): + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id), + ) + and not result_ready[0] + and request_num[0] == 5 + ): result_ready[0] = True request_num[0] += 1 return MockResponse({"status": "RUNNING"}, 200) - elif (args[1] == urljoin( + 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] == 7): + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id), + ) + and result_ready[0] + and request_num[0] == 7 + ): request_num[0] += 1 - return MockResponse( - {"status": "COMPLETED"}, 200) - #STEP6 - elif (args[1] == urljoin( + return MockResponse({"status": "COMPLETED"}, 200) + # STEP6 + elif ( + args[1] + == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloadUrl". - format(execution_id=execution_id)) - and request_num[0] == 8): + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloadUrl".format( + execution_id=execution_id + ), + ) + and request_num[0] == 8 + ): request_num[0] += 1 - return MockResponse( - {"url": "result_download_url"}, 200) - #STEP7 - elif (args[1] == "result_download_url" - and request_num[0] == 9): + return MockResponse({"url": "result_download_url"}, 200) + # STEP7 + elif args[1] == "result_download_url" and request_num[0] == 9: request_num[0] += 1 - return MockResponse( - {"results": [result]}, 200) + return MockResponse({"results": [result]}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -145,37 +163,40 @@ def raise_for_status(self): 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}) # STEP1 - elif (args[1] == urljoin(_API_URL, jobs_url) - and request_num[0] == 2): + elif args[1] == urljoin(_API_URL, jobs_url) and request_num[0] == 2: request_num[0] += 1 - answer1={'objectStorageInfo':{ - 'downloadQObjectUrlEndpoint':'url_dld_endpoint', - 'uploadQobjectUrlEndpoint':'/'+execution_id+'/jobUploadUrl', - 'uploadUrl':'url_upld'}, - 'id': execution_id + answer1 = { + 'objectStorageInfo': { + 'downloadQObjectUrlEndpoint': 'url_dld_endpoint', + 'uploadQobjectUrlEndpoint': '/' + execution_id + '/jobUploadUrl', + 'uploadUrl': 'url_upld', + }, + 'id': execution_id, } - return MockPostResponse(answer1,200) + return MockPostResponse(answer1, 200) # STEP4 - elif (args[1] == urljoin(_API_URL, jobs_url + "/"+execution_id+"/jobDataUploaded") - and request_num[0] == 4): + elif args[1] == urljoin(_API_URL, jobs_url + "/" + execution_id + "/jobDataUploaded") and request_num[0] == 4: request_num[0] += 1 return MockPostResponse({}, 200) - #STEP8 - elif (args[1] == urljoin( + # STEP8 + elif ( + args[1] + == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded". - format(execution_id=execution_id)) - and request_num[0] == 10): + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded".format( + execution_id=execution_id + ), + ) + and request_num[0] == 10 + ): request_num[0] += 1 - return MockPostResponse( - {}, 200) + return MockPostResponse({}, 200) def mocked_requests_put(*args, **kwargs): class MockRequest: @@ -196,12 +217,10 @@ def raise_for_status(self): pass # STEP3 - if (args[1] == "url_upld" - and request_num[0] == 3): + if args[1] == "url_upld" and request_num[0] == 3: request_num[0] += 1 return MockResponse({}, 200) - monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) monkeypatch.setattr("requests.sessions.Session.post", mocked_requests_post) monkeypatch.setattr("requests.sessions.Session.put", mocked_requests_put) @@ -213,21 +232,13 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) # Code to test: - res = _ibm_http_client.send(json_qasm, - device="ibmqx4", - token=None, - shots=shots, - verbose=True) + res = _ibm_http_client.send(json_qasm, device="ibmqx4", 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) + res = _ibm_http_client.send(json_qasm, device="ibmqx4", token=None, shots=shots, verbose=True) def test_no_password_given(monkeypatch): @@ -241,11 +252,7 @@ def user_password_input(prompt): 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) + _ibm_http_client.send(json_qasm, device="ibmqx4", token=None, shots=1, verbose=True) def test_send_real_device_offline(monkeypatch): @@ -289,7 +296,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) @@ -298,21 +305,14 @@ def raise_for_status(self): shots = 1 token = '12345' json_qasm = { - 'qasms': [{ - 'qasm': 'my qasm' - }], + 'qasms': [{'qasm': 'my qasm'}], 'shots': 1, 'json': 'instructions', 'maxCredits': 10, - 'nq': 1 + '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) + _ibm_http_client.send(json_qasm, device="ibmqx4", token=token, shots=shots, verbose=True) def test_show_device(monkeypatch): @@ -334,14 +334,32 @@ 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) + 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: @@ -362,7 +380,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) @@ -377,10 +395,21 @@ def user_password_input(prompt): 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)}, + '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 + 'nq': 32, } } @@ -405,28 +434,17 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 json_qasm = { - 'qasms': [{ - 'qasm': 'my qasm' - }], + 'qasms': [{'qasm': 'my qasm'}], 'shots': 1, 'json': 'instructions', 'maxCredits': 10, - 'nq': 1 + 'nq': 1, } - name = 'projectq_test' - _ibm_http_client.send(json_qasm, - device="ibmqx4", - token=None, - shots=shots, - verbose=True) + _ibm_http_client.send(json_qasm, device="ibmqx4", token=None, shots=shots, verbose=True) token = '' with pytest.raises(Exception): - _ibm_http_client.send(json_qasm, - device="ibmqx4", - token=None, - shots=shots, - verbose=True) + _ibm_http_client.send(json_qasm, device="ibmqx4", token=None, shots=shots, verbose=True) def test_send_that_errors_are_caught2(monkeypatch): @@ -449,20 +467,13 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 json_qasm = { - 'qasms': [{ - 'qasm': 'my qasm' - }], + 'qasms': [{'qasm': 'my qasm'}], 'shots': 1, 'json': 'instructions', 'maxCredits': 10, - 'nq': 1 + 'nq': 1, } - name = 'projectq_test' - _ibm_http_client.send(json_qasm, - device="ibmqx4", - token=None, - shots=shots, - verbose=True) + _ibm_http_client.send(json_qasm, device="ibmqx4", token=None, shots=shots, verbose=True) def test_send_that_errors_are_caught3(monkeypatch): @@ -485,31 +496,22 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) shots = 1 json_qasm = { - 'qasms': [{ - 'qasm': 'my qasm' - }], + 'qasms': [{'qasm': 'my qasm'}], 'shots': 1, 'json': 'instructions', 'maxCredits': 10, - 'nq': 1 + 'nq': 1, } - name = 'projectq_test' - _ibm_http_client.send(json_qasm, - device="ibmqx4", - token=None, - shots=shots, - verbose=True) + _ibm_http_client.send(json_qasm, device="ibmqx4", token=None, shots=shots, verbose=True) def test_timeout_exception(monkeypatch): qasms = { - 'qasms': [{ - 'qasm': 'my qasm' - }], + 'qasms': [{'qasm': 'my qasm'}], 'shots': 1, 'json': 'instructions', 'maxCredits': 10, - 'nq': 1 + 'nq': 1, } json_qasm = qasms tries = [0] @@ -530,28 +532,45 @@ 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( - execution_id) + 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(execution_id) if args[1] == urljoin(_API_URL, job_url): tries[0] += 1 return MockResponse({"status": "RUNNING"}, 200) - #STEP2 - elif (args[1] == "/"+execution_id+"/jobUploadUrl"): + # STEP2 + elif args[1] == "/" + execution_id + "/jobUploadUrl": return MockResponse({"url": "s3_url"}, 200) - #STEP5 - elif (args[1] == urljoin( - _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}". - format(execution_id=execution_id))): + # STEP5 + elif args[1] == urljoin( + _API_URL, + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id), + ): return MockResponse({"status": "RUNNING"}, 200) def mocked_requests_post(*args, **kwargs): @@ -576,17 +595,19 @@ def raise_for_status(self): return MockPostResponse({"userId": "1", "id": "12"}) # STEP1 - elif (args[1] == urljoin(_API_URL, jobs_url)): - answer1={'objectStorageInfo':{ - 'downloadQObjectUrlEndpoint':'url_dld_endpoint', - 'uploadQobjectUrlEndpoint':'/'+execution_id+'/jobUploadUrl', - 'uploadUrl':'url_upld'}, - 'id': execution_id, + elif args[1] == urljoin(_API_URL, jobs_url): + answer1 = { + 'objectStorageInfo': { + 'downloadQObjectUrlEndpoint': 'url_dld_endpoint', + 'uploadQobjectUrlEndpoint': '/' + execution_id + '/jobUploadUrl', + 'uploadUrl': 'url_upld', + }, + 'id': execution_id, } - return MockPostResponse(answer1,200) + return MockPostResponse(answer1, 200) # STEP4 - elif (args[1] == urljoin(_API_URL, jobs_url + "/"+execution_id+"/jobDataUploaded")): + elif args[1] == urljoin(_API_URL, jobs_url + "/" + execution_id + "/jobDataUploaded"): return MockPostResponse({}, 200) def mocked_requests_put(*args, **kwargs): @@ -608,7 +629,7 @@ def raise_for_status(self): pass # STEP3 - if (args[1] == "url_upld"): + if args[1] == "url_upld": return MockResponse({}, 200) monkeypatch.setattr("requests.sessions.Session.get", mocked_requests_get) @@ -617,12 +638,14 @@ def raise_for_status(self): _ibm_http_client.time.sleep = lambda x: x with pytest.raises(Exception) as excinfo: - _ibm_http_client.send(json_qasm, - device="ibmqx4", - token="test", - shots=1, - num_retries=10, - verbose=False) + _ibm_http_client.send( + json_qasm, + device="ibmqx4", + token="test", + shots=1, + num_retries=10, + verbose=False, + ) assert execution_id in str(excinfo.value) # check that job id is in exception assert tries[0] > 0 @@ -645,39 +668,37 @@ 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 = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format( - "123e") - err_url = "Network/ibm-q/Groups/open/Projects/main/Jobs/{}".format( - "123ee") + 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 = "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( - { - "status": "RUNNING", - 'iteration': request_num[0] - }, 200) + return MockResponse({"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) + return MockResponse({"status": "TERMINATED", 'iteration': request_num[0]}, 400) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -704,20 +725,14 @@ 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="test", - jobid="123e", - num_retries=200) + _ibm_http_client.retrieve(device="ibmqx4", token="test", jobid="123e", num_retries=200) with pytest.raises(Exception): - _ibm_http_client.retrieve(device="ibmqx4", - token="test", - jobid="123ee", - num_retries=200) + _ibm_http_client.retrieve(device="ibmqx4", token="test", jobid="123ee", num_retries=200) def test_retrieve(monkeypatch): request_num = [0] - execution_id='3' + execution_id = '3' def mocked_requests_get(*args, **kwargs): class MockResponse: @@ -734,37 +749,45 @@ 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) - - #STEP5 - elif (args[1] == urljoin( + return MockResponse( + [ + { + 'backend_name': 'ibmqx4', + 'coupling_map': None, + 'backend_version': '0.1.547', + 'n_qubits': 32, + } + ], + 200, + ) + + # STEP5 + elif ( + args[1] + == urljoin( _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}". - format(execution_id=execution_id))and request_num[0] < 1): + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id), + ) + and request_num[0] < 1 + ): 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))): - return MockResponse( - {"status": "COMPLETED"}, 200) - #STEP6 - elif (args[1] == urljoin( - _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloadUrl". - format(execution_id=execution_id))): - return MockResponse( - {"url": "result_download_url"}, 200) - #STEP7 - elif (args[1] == "result_download_url"): - return MockResponse( - {"results": ['correct']}, 200) + elif args[1] == urljoin( + _API_URL, + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}".format(execution_id=execution_id), + ): + return MockResponse({"status": "COMPLETED"}, 200) + # STEP6 + elif args[1] == urljoin( + _API_URL, + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloadUrl".format( + execution_id=execution_id + ), + ): + return MockResponse({"url": "result_download_url"}, 200) + # STEP7 + elif args[1] == "result_download_url": + return MockResponse({"results": ['correct']}, 200) def mocked_requests_post(*args, **kwargs): class MockRequest: @@ -786,19 +809,18 @@ def raise_for_status(self): if args[1] == _AUTH_API_URL: return MockPostResponse({"userId": "1", "id": "12"}) - #STEP8 - elif (args[1] == urljoin( - _API_URL, - "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded". - format(execution_id=execution_id))): - return MockPostResponse( - {}, 200) + # STEP8 + elif args[1] == urljoin( + _API_URL, + "Network/ibm-q/Groups/open/Projects/main/Jobs/{execution_id}/resultDownloaded".format( + execution_id=execution_id + ), + ): + return MockPostResponse({}, 200) 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", - jobid=execution_id) + res = _ibm_http_client.retrieve(device="ibmqx4", token="test", jobid=execution_id) assert res == 'correct' diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index f6890d34c..27249161d 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +19,30 @@ from projectq.setups import restrictedgateset from projectq import MainEngine from projectq.backends._ibm import _ibm -from projectq.cengines import (BasicMapperEngine, DummyEngine) +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) +from projectq.ops import ( + All, + Allocate, + Barrier, + Command, + Deallocate, + Entangle, + Measure, + NOT, + Rx, + Ry, + Rz, + S, + Sdag, + T, + Tdag, + X, + Y, + Z, + H, + CNOT, +) # Insure that no HTTP request can be made in all tests in this module @@ -31,29 +51,43 @@ 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 @@ -114,44 +148,60 @@ def test_ibm_retrieve(monkeypatch): # patch send def mock_retrieve(*args, **kwargs): return { - 'data': { - 'counts': { - '0x0': 504, - '0x2': 8, - '0xc': 6, - '0xe': 482 - } - }, + '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]] + '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 + 'parallel_state_update': 16, }, 'seed_simulator': 465435780, 'shots': 1000, 'status': 'DONE', 'success': True, - 'time_taken': 0.0045786460000000005 + 'time_taken': 0.0045786460000000005, } monkeypatch.setattr(_ibm, "retrieve", mock_retrieve) @@ -162,8 +212,7 @@ def mock_retrieve(*args, **kwargs): 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 = 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() @@ -187,111 +236,94 @@ 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' - } + '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 - } - }, + '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]] + '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 + 'parallel_state_update': 16, }, 'seed_simulator': 465435780, 'shots': 1000, 'status': 'DONE', 'success': True, - 'time_taken': 0.0045786460000000005 + 'time_taken': 0.0045786460000000005, } 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([]) @@ -301,9 +333,9 @@ def mock_send(*args, **kwargs): 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, )) + 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 diff --git a/projectq/backends/_printer.py b/projectq/backends/_printer.py index 7f00c677d..69223b706 100755 --- a/projectq/backends/_printer.py +++ b/projectq/backends/_printer.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Contains a compiler engine which prints commands to stdout prior to sending them on to the next engines (see CommandPrinter). @@ -31,8 +31,8 @@ class CommandPrinter(BasicEngine): CommandPrinter is a compiler engine which prints commands to stdout prior to sending them on to the next compiler engine. """ - def __init__(self, accept_input=True, default_measure=False, - in_place=False): + + def __init__(self, accept_input=True, default_measure=False, in_place=False): """ Initialize a CommandPrinter. @@ -79,15 +79,14 @@ def _print_cmd(self, cmd): cmd (Command): Command to print. """ if self.is_last_engine and cmd.gate == Measure: - assert(get_control_count(cmd) == 0) + assert get_control_count(cmd) == 0 print(cmd) for qureg in cmd.qubits: for qubit in qureg: if self._accept_input: m = None while m != '0' and m != '1' and m != 1 and m != 0: - prompt = ("Input measurement result (0 or 1) for" - " qubit " + str(qubit) + ": ") + prompt = "Input measurement result (0 or 1) for qubit " + str(qubit) + ": " m = input(prompt) else: m = self._default_measure @@ -98,11 +97,10 @@ def _print_cmd(self, cmd): if isinstance(tag, LogicalQubitIDTag): logical_id_tag = tag if logical_id_tag is not None: - qubit = WeakQubitRef(qubit.engine, - logical_id_tag.logical_qubit_id) + qubit = WeakQubitRef(qubit.engine, logical_id_tag.logical_qubit_id) self.main_engine.set_measurement_result(qubit, m) else: - if self._in_place: + if self._in_place: # pragma: no cover sys.stdout.write("\0\r\t\x1b[K" + str(cmd) + "\r") else: print(cmd) diff --git a/projectq/backends/_printer_test.py b/projectq/backends/_printer_test.py index 4c76425e2..872423e26 100755 --- a/projectq/backends/_printer_test.py +++ b/projectq/backends/_printer_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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._printer.py. """ @@ -19,9 +19,7 @@ import pytest from projectq import MainEngine -from projectq.cengines import (DummyEngine, - InstructionFilter, - NotYetMeasuredError) +from projectq.cengines import DummyEngine, InstructionFilter, NotYetMeasuredError from projectq.meta import LogicalQubitIDTag from projectq.ops import Allocate, Command, H, Measure, NOT, T from projectq.types import WeakQubitRef @@ -35,9 +33,9 @@ def test_command_printer_is_available(): def available_cmd(self, cmd): return cmd.gate == H + filter = InstructionFilter(available_cmd) - eng = MainEngine(backend=cmd_printer, - engine_list=[inline_cmd_printer, filter]) + eng = MainEngine(backend=cmd_printer, engine_list=[inline_cmd_printer, filter]) qubit = eng.allocate_qubit() cmd0 = Command(eng, H, (qubit,)) cmd1 = Command(eng, T, (qubit,)) @@ -75,8 +73,13 @@ def test_command_printer_measure_mapped_qubit(): qb1 = WeakQubitRef(engine=eng, idx=1) qb2 = WeakQubitRef(engine=eng, idx=2) cmd0 = Command(engine=eng, gate=Allocate, qubits=([qb1],)) - cmd1 = Command(engine=eng, gate=Measure, qubits=([qb1],), controls=[], - tags=[LogicalQubitIDTag(2)]) + cmd1 = Command( + engine=eng, + gate=Measure, + qubits=([qb1],), + controls=[], + tags=[LogicalQubitIDTag(2)], + ) with pytest.raises(NotYetMeasuredError): int(qb1) with pytest.raises(NotYetMeasuredError): diff --git a/projectq/backends/_resource.py b/projectq/backends/_resource.py index 7d9b56117..97317778f 100755 --- a/projectq/backends/_resource.py +++ b/projectq/backends/_resource.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Contains a compiler engine which counts the number of calls for each type of gate used in a circuit, in addition to the max. number of active qubits. @@ -41,6 +41,7 @@ class ResourceCounter(BasicEngine): depth_of_dag (int): It is the longest path in the directed acyclic graph (DAG) of the program. """ + def __init__(self): """ Initialize a resource counter engine. @@ -104,8 +105,7 @@ def _add_cmd(self, cmd): if isinstance(tag, LogicalQubitIDTag): logical_id_tag = tag if logical_id_tag is not None: - qubit = WeakQubitRef(qubit.engine, - logical_id_tag.logical_qubit_id) + qubit = WeakQubitRef(qubit.engine, logical_id_tag.logical_qubit_id) self.main_engine.set_measurement_result(qubit, 0) else: qubit_ids = set() @@ -159,12 +159,15 @@ def __str__(self): gate_name = ctrl_cnt * "C" + str(gate) gate_list.append(gate_name + " : " + str(num)) - return ("Gate class counts:\n " + - "\n ".join(list(sorted(gate_class_list))) + - "\n\nGate counts:\n " + - "\n ".join(list(sorted(gate_list))) + - "\n\nMax. width (number of qubits) : " + - str(self.max_width) + ".") + return ( + "Gate class counts:\n " + + "\n ".join(list(sorted(gate_class_list))) + + "\n\nGate counts:\n " + + "\n ".join(list(sorted(gate_list))) + + "\n\nMax. width (number of qubits) : " + + str(self.max_width) + + "." + ) return "(No quantum resources used)" def receive(self, command_list): diff --git a/projectq/backends/_resource_test.py b/projectq/backends/_resource_test.py index cf7122c01..9031d9cc9 100755 --- a/projectq/backends/_resource_test.py +++ b/projectq/backends/_resource_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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._resource.py. """ @@ -46,8 +46,13 @@ def test_resource_counter_measurement(): qb1 = WeakQubitRef(engine=eng, idx=1) qb2 = WeakQubitRef(engine=eng, idx=2) cmd0 = Command(engine=eng, gate=Allocate, qubits=([qb1],)) - cmd1 = Command(engine=eng, gate=Measure, qubits=([qb1],), controls=[], - tags=[LogicalQubitIDTag(2)]) + cmd1 = Command( + engine=eng, + gate=Measure, + qubits=([qb1],), + controls=[], + tags=[LogicalQubitIDTag(2)], + ) with pytest.raises(NotYetMeasuredError): int(qb1) with pytest.raises(NotYetMeasuredError): diff --git a/projectq/backends/_sim/__init__.py b/projectq/backends/_sim/__init__.py index d225b59e0..513e6d4b0 100755 --- a/projectq/backends/_sim/__init__.py +++ b/projectq/backends/_sim/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/backends/_sim/_classical_simulator.py b/projectq/backends/_sim/_classical_simulator.py index 821c01de9..3d93c1467 100755 --- a/projectq/backends/_sim/_classical_simulator.py +++ b/projectq/backends/_sim/_classical_simulator.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,19 +12,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. - """ A simulator that only permits classical operations, for faster/easier testing. """ from projectq.cengines import BasicEngine from projectq.meta import LogicalQubitIDTag -from projectq.ops import (XGate, - BasicMathGate, - Measure, - FlushGate, - Allocate, - Deallocate) +from projectq.ops import XGate, BasicMathGate, Measure, FlushGate, Allocate, Deallocate from projectq.types import WeakQubitRef @@ -35,6 +30,7 @@ class ClassicalSimulator(BasicEngine): controls, NOTs, and any BasicMathGate. Supports reading/writing directly from/to bits and registers of bits. """ + def __init__(self): BasicEngine.__init__(self) self._state = 0 @@ -50,11 +46,8 @@ def _convert_logical_to_mapped_qubit(self, qubit): mapper = self.main_engine.mapper if mapper is not None: if qubit.id not in mapper.current_mapping: - raise RuntimeError("Unknown qubit id. " - "Please make sure you have called " - "eng.flush().") - return WeakQubitRef(qubit.engine, - mapper.current_mapping[qubit.id]) + raise RuntimeError("Unknown qubit id. Please make sure you have called eng.flush().") + return WeakQubitRef(qubit.engine, mapper.current_mapping[qubit.id]) else: return qubit @@ -77,7 +70,7 @@ def read_bit(self, qubit): return self._read_mapped_bit(qubit) def _read_mapped_bit(self, mapped_qubit): - """ Internal use only. Does not change logical to mapped qubits.""" + """Internal use only. Does not change logical to mapped qubits.""" p = self._bit_positions[mapped_qubit.id] return (self._state >> p) & 1 @@ -98,7 +91,7 @@ def write_bit(self, qubit, value): self._write_mapped_bit(qubit, value) def _write_mapped_bit(self, mapped_qubit, value): - """ Internal use only. Does not change logical to mapped qubits.""" + """Internal use only. Does not change logical to mapped qubits.""" p = self._bit_positions[mapped_qubit.id] if value: self._state |= 1 << p @@ -144,7 +137,7 @@ def read_register(self, qureg): return self._read_mapped_register(new_qureg) def _read_mapped_register(self, mapped_qureg): - """ Internal use only. Does not change logical to mapped qubits.""" + """Internal use only. Does not change logical to mapped qubits.""" t = 0 for i in range(len(mapped_qureg)): t |= self._read_mapped_bit(mapped_qureg[i]) << i @@ -170,19 +163,21 @@ def write_register(self, qureg, value): self._write_mapped_register(new_qureg, value) def _write_mapped_register(self, mapped_qureg, value): - """ Internal use only. Does not change logical to mapped qubits.""" + """Internal use only. Does not change logical to mapped qubits.""" if value < 0 or value >= 1 << len(mapped_qureg): raise ValueError("Value won't fit in register.") for i in range(len(mapped_qureg)): self._write_mapped_bit(mapped_qureg[i], (value >> i) & 1) def is_available(self, cmd): - return (cmd.gate == Measure or - cmd.gate == Allocate or - cmd.gate == Deallocate or - isinstance(cmd.gate, BasicMathGate) or - isinstance(cmd.gate, FlushGate) or - isinstance(cmd.gate, XGate)) + return ( + cmd.gate == Measure + or cmd.gate == Allocate + or cmd.gate == Deallocate + or isinstance(cmd.gate, BasicMathGate) + or isinstance(cmd.gate, FlushGate) + or isinstance(cmd.gate, XGate) + ) def receive(self, command_list): for cmd in command_list: @@ -204,10 +199,8 @@ def _handle(self, cmd): logical_id_tag = tag log_qb = qb if logical_id_tag is not None: - log_qb = WeakQubitRef(qb.engine, - logical_id_tag.logical_qubit_id) - self.main_engine.set_measurement_result( - log_qb, self._read_mapped_bit(qb)) + log_qb = WeakQubitRef(qb.engine, logical_id_tag.logical_qubit_id) + self.main_engine.set_measurement_result(log_qb, self._read_mapped_bit(qb)) return if cmd.gate == Allocate: @@ -219,11 +212,9 @@ def _handle(self, cmd): old_id = cmd.qubits[0][0].id pos = self._bit_positions[old_id] low = (1 << pos) - 1 + self._state = (self._state & low) | ((self._state >> 1) & ~low) - self._bit_positions = { - k: b - (0 if b < pos else 1) - for k, b in self._bit_positions.items() - } + self._bit_positions = {k: b - (0 if b < pos else 1) for k, b in self._bit_positions.items() if k != old_id} return controls_mask = self._mask(cmd.control_qubits) @@ -233,8 +224,7 @@ def _handle(self, cmd): assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 target = cmd.qubits[0][0] if meets_controls: - self._write_mapped_bit(target, - not self._read_mapped_bit(target)) + self._write_mapped_bit(target, not self._read_mapped_bit(target)) return if isinstance(cmd.gate, BasicMathGate): @@ -242,8 +232,7 @@ def _handle(self, cmd): ins = [self._read_mapped_register(reg) for reg in cmd.qubits] outs = cmd.gate.get_math_function(cmd.qubits)(ins) for reg, out in zip(cmd.qubits, outs): - self._write_mapped_register(reg, - out & ((1 << len(reg)) - 1)) + self._write_mapped_register(reg, out & ((1 << len(reg)) - 1)) return raise ValueError("Only support alloc/dealloc/measure/not/math ops.") diff --git a/projectq/backends/_sim/_classical_simulator_test.py b/projectq/backends/_sim/_classical_simulator_test.py index afcd266b9..f9c33dc90 100755 --- a/projectq/backends/_sim/_classical_simulator_test.py +++ b/projectq/backends/_sim/_classical_simulator_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,17 +16,28 @@ import pytest from projectq import MainEngine -from projectq.ops import (All, Allocate, BasicMathGate, C, Command, Deallocate, - FlushGate, Measure, NOT, X, Y) -from projectq.cengines import (AutoReplacer, BasicMapperEngine, - DecompositionRuleSet, DummyEngine) -from ._simulator_test import mapper +from projectq.ops import ( + All, + BasicMathGate, + C, + Measure, + NOT, + X, + Y, +) +from projectq.cengines import ( + AutoReplacer, + BasicMapperEngine, + DecompositionRuleSet, + DummyEngine, +) +from ._simulator_test import mapper # noqa: F401 from projectq.types import WeakQubitRef from ._classical_simulator import ClassicalSimulator -def test_simulator_read_write(mapper): +def test_simulator_read_write(mapper): # noqa: F811 engine_list = [] if mapper is not None: engine_list.append(mapper) @@ -53,7 +65,7 @@ def test_simulator_read_write(mapper): assert sim.read_bit(b[0]) == 1 -def test_simulator_triangle_increment_cycle(mapper): +def test_simulator_triangle_increment_cycle(mapper): # noqa: F811 engine_list = [] if mapper is not None: engine_list.append(mapper) @@ -68,7 +80,7 @@ def test_simulator_triangle_increment_cycle(mapper): assert sim.read_register(a) == 0 -def test_simulator_bit_repositioning(mapper): +def test_simulator_bit_repositioning(mapper): # noqa: F811 engine_list = [] if mapper is not None: engine_list.append(mapper) @@ -82,18 +94,20 @@ def test_simulator_bit_repositioning(mapper): sim.write_register(c, 33) for q in b: eng.deallocate_qubit(q) + # Make sure that the qubit are marked as deleted + assert q.id == -1 assert sim.read_register(a) == 9 assert sim.read_register(c) == 33 -def test_simulator_arithmetic(mapper): +def test_simulator_arithmetic(mapper): # noqa: F811 class Offset(BasicMathGate): def __init__(self, amount): - BasicMathGate.__init__(self, lambda x: (x+amount,)) + BasicMathGate.__init__(self, lambda x: (x + amount,)) class Sub(BasicMathGate): def __init__(self): - BasicMathGate.__init__(self, lambda x, y: (x, y-x)) + BasicMathGate.__init__(self, lambda x, y: (x, y - x)) engine_list = [] if mapper is not None: @@ -136,7 +150,7 @@ def __init__(self): assert int(b[i]) == ((24 >> i) & 1) -def test_write_register_value_error_exception(mapper): +def test_write_register_value_error_exception(mapper): # noqa: F811 engine_list = [] if mapper is not None: engine_list.append(mapper) @@ -180,7 +194,7 @@ def test_wrong_gate(): def test_runtime_error(): sim = ClassicalSimulator() - mapper = BasicMapperEngine() + mapper = BasicMapperEngine() # noqa: F811 mapper.current_mapping = {} eng = MainEngine(sim, [mapper]) with pytest.raises(RuntimeError): diff --git a/projectq/backends/_sim/_cppkernels/intrin/alignedallocator.hpp b/projectq/backends/_sim/_cppkernels/intrin/alignedallocator.hpp index 02e0ead2b..7719f2d06 100755 --- a/projectq/backends/_sim/_cppkernels/intrin/alignedallocator.hpp +++ b/projectq/backends/_sim/_cppkernels/intrin/alignedallocator.hpp @@ -117,4 +117,3 @@ class aligned_allocator #if __cplusplus < 201103L #undef noexcept #endif - diff --git a/projectq/backends/_sim/_cppkernels/intrin/kernel1.hpp b/projectq/backends/_sim/_cppkernels/intrin/kernel1.hpp index 3ca031ab2..793a116fb 100755 --- a/projectq/backends/_sim/_cppkernels/intrin/kernel1.hpp +++ b/projectq/backends/_sim/_cppkernels/intrin/kernel1.hpp @@ -60,4 +60,3 @@ void kernel(V &psi, unsigned id0, M const& m, std::size_t ctrlmask) } } } - diff --git a/projectq/backends/_sim/_cppkernels/intrin/kernel2.hpp b/projectq/backends/_sim/_cppkernels/intrin/kernel2.hpp index b355acd32..e1a2c9a9b 100755 --- a/projectq/backends/_sim/_cppkernels/intrin/kernel2.hpp +++ b/projectq/backends/_sim/_cppkernels/intrin/kernel2.hpp @@ -69,4 +69,3 @@ void kernel(V &psi, unsigned id1, unsigned id0, M const& m, std::size_t ctrlmask } } } - diff --git a/projectq/backends/_sim/_cppkernels/intrin/kernel3.hpp b/projectq/backends/_sim/_cppkernels/intrin/kernel3.hpp index 7f20db0d4..2aac0f8a8 100755 --- a/projectq/backends/_sim/_cppkernels/intrin/kernel3.hpp +++ b/projectq/backends/_sim/_cppkernels/intrin/kernel3.hpp @@ -88,4 +88,3 @@ void kernel(V &psi, unsigned id2, unsigned id1, unsigned id0, M const& m, std::s } } } - diff --git a/projectq/backends/_sim/_cppkernels/intrin/kernel4.hpp b/projectq/backends/_sim/_cppkernels/intrin/kernel4.hpp index 9ff66eca3..5523a556c 100755 --- a/projectq/backends/_sim/_cppkernels/intrin/kernel4.hpp +++ b/projectq/backends/_sim/_cppkernels/intrin/kernel4.hpp @@ -129,4 +129,3 @@ void kernel(V &psi, unsigned id3, unsigned id2, unsigned id1, unsigned id0, M co } } } - diff --git a/projectq/backends/_sim/_cppkernels/intrin/kernel5.hpp b/projectq/backends/_sim/_cppkernels/intrin/kernel5.hpp index 6fc6cf751..9cf781fa0 100755 --- a/projectq/backends/_sim/_cppkernels/intrin/kernel5.hpp +++ b/projectq/backends/_sim/_cppkernels/intrin/kernel5.hpp @@ -254,4 +254,3 @@ void kernel(V &psi, unsigned id4, unsigned id3, unsigned id2, unsigned id1, unsi } } } - diff --git a/projectq/backends/_sim/_cppkernels/intrin/kernels.hpp b/projectq/backends/_sim/_cppkernels/intrin/kernels.hpp index e59c94168..f592142da 100755 --- a/projectq/backends/_sim/_cppkernels/intrin/kernels.hpp +++ b/projectq/backends/_sim/_cppkernels/intrin/kernels.hpp @@ -32,4 +32,3 @@ #include "kernel3.hpp" #include "kernel4.hpp" #include "kernel5.hpp" - diff --git a/projectq/backends/_sim/_cppkernels/nointrin/kernel1.hpp b/projectq/backends/_sim/_cppkernels/nointrin/kernel1.hpp index bf3bf5a40..e1cd9e660 100755 --- a/projectq/backends/_sim/_cppkernels/nointrin/kernel1.hpp +++ b/projectq/backends/_sim/_cppkernels/nointrin/kernel1.hpp @@ -51,4 +51,3 @@ void kernel(V &psi, unsigned id0, M const& m, std::size_t ctrlmask) } } } - diff --git a/projectq/backends/_sim/_cppkernels/nointrin/kernel2.hpp b/projectq/backends/_sim/_cppkernels/nointrin/kernel2.hpp index 98809d97c..879fa8572 100755 --- a/projectq/backends/_sim/_cppkernels/nointrin/kernel2.hpp +++ b/projectq/backends/_sim/_cppkernels/nointrin/kernel2.hpp @@ -60,4 +60,3 @@ void kernel(V &psi, unsigned id1, unsigned id0, M const& m, std::size_t ctrlmask } } } - diff --git a/projectq/backends/_sim/_cppkernels/nointrin/kernel3.hpp b/projectq/backends/_sim/_cppkernels/nointrin/kernel3.hpp index 8d79f55fc..05b68afef 100755 --- a/projectq/backends/_sim/_cppkernels/nointrin/kernel3.hpp +++ b/projectq/backends/_sim/_cppkernels/nointrin/kernel3.hpp @@ -85,4 +85,3 @@ void kernel(V &psi, unsigned id2, unsigned id1, unsigned id0, M const& m, std::s } } } - diff --git a/projectq/backends/_sim/_cppkernels/nointrin/kernel4.hpp b/projectq/backends/_sim/_cppkernels/nointrin/kernel4.hpp index 9e0e9ee51..b12424a7c 100755 --- a/projectq/backends/_sim/_cppkernels/nointrin/kernel4.hpp +++ b/projectq/backends/_sim/_cppkernels/nointrin/kernel4.hpp @@ -150,4 +150,3 @@ void kernel(V &psi, unsigned id3, unsigned id2, unsigned id1, unsigned id0, M co } } } - diff --git a/projectq/backends/_sim/_cppkernels/nointrin/kernel5.hpp b/projectq/backends/_sim/_cppkernels/nointrin/kernel5.hpp index 9480eaa65..a3e47f10f 100755 --- a/projectq/backends/_sim/_cppkernels/nointrin/kernel5.hpp +++ b/projectq/backends/_sim/_cppkernels/nointrin/kernel5.hpp @@ -371,4 +371,3 @@ void kernel(V &psi, unsigned id4, unsigned id3, unsigned id2, unsigned id1, unsi } } } - diff --git a/projectq/backends/_sim/_cppkernels/simulator.hpp b/projectq/backends/_sim/_cppkernels/simulator.hpp index d248ed038..19e4b173c 100755 --- a/projectq/backends/_sim/_cppkernels/simulator.hpp +++ b/projectq/backends/_sim/_cppkernels/simulator.hpp @@ -268,21 +268,21 @@ class Simulator{ std::swap(tmpBuff1_, newvec); } - // faster version without calling python + // faster version without calling python template inline void emulate_math_addConstant(int a, const QuReg& quregs, const std::vector& ctrl) { emulate_math([a](std::vector &res){for(auto& x: res) x = x + a;}, quregs, ctrl, true); } - // faster version without calling python + // faster version without calling python template inline void emulate_math_addConstantModN(int a, int N, const QuReg& quregs, const std::vector& ctrl) { emulate_math([a,N](std::vector &res){for(auto& x: res) x = (x + a) % N;}, quregs, ctrl, true); } - // faster version without calling python + // faster version without calling python template inline void emulate_math_multiplyByConstantModN(int a, int N, const QuReg& quregs, const std::vector& ctrl) { diff --git a/projectq/backends/_sim/_cppsim.cpp b/projectq/backends/_sim/_cppsim.cpp index cab68d0ee..2402812bf 100755 --- a/projectq/backends/_sim/_cppsim.cpp +++ b/projectq/backends/_sim/_cppsim.cpp @@ -34,13 +34,14 @@ template void emulate_math_wrapper(Simulator &sim, py::function const& pyfunc, QR const& qr, std::vector const& ctrls){ auto f = [&](std::vector& x) { pybind11::gil_scoped_acquire acquire; - x = std::move(pyfunc(x).cast>()); + x = pyfunc(x).cast>(); }; pybind11::gil_scoped_release release; sim.emulate_math(f, qr, ctrls); } -PYBIND11_PLUGIN(_cppsim) { - py::module m("_cppsim", "_cppsim"); + +PYBIND11_MODULE(_cppsim, m) +{ py::class_(m, "Simulator") .def(py::init()) .def("allocate_qubit", &Simulator::allocate_qubit) @@ -63,5 +64,4 @@ PYBIND11_PLUGIN(_cppsim) { .def("run", &Simulator::run) .def("cheat", &Simulator::cheat) ; - return m.ptr(); } diff --git a/projectq/backends/_sim/_pysim.py b/projectq/backends/_sim/_pysim.py index 58bff2ec2..3900b4b7b 100755 --- a/projectq/backends/_sim/_pysim.py +++ b/projectq/backends/_sim/_pysim.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Contains a (slow) Python simulator. @@ -23,7 +23,7 @@ import os _USE_REFCHECK = True -if 'TRAVIS' in os.environ: +if 'TRAVIS' in os.environ: # pragma: no cover _USE_REFCHECK = False @@ -35,6 +35,7 @@ class Simulator(object): not an option (for some reason). It has the same features but is much slower, so please consider building the c++ version for larger experiments. """ + def __init__(self, rnd_seed, *args, **kwargs): """ Initialize the simulator. @@ -78,7 +79,7 @@ def measure_qubits(self, ids): List of measurement results (containing either True or False). """ P = random.random() - val = 0. + val = 0.0 i_picked = 0 while val < P and i_picked < len(self._state): val += _np.abs(self._state[i_picked]) ** 2 @@ -92,18 +93,18 @@ def measure_qubits(self, ids): mask = 0 val = 0 for i in range(len(pos)): - res[i] = (((i_picked >> pos[i]) & 1) == 1) - mask |= (1 << pos[i]) - val |= ((res[i] & 1) << pos[i]) + res[i] = ((i_picked >> pos[i]) & 1) == 1 + mask |= 1 << pos[i] + val |= (res[i] & 1) << pos[i] - nrm = 0. + nrm = 0.0 for i in range(len(self._state)): if (mask & i) != val: - self._state[i] = 0. + self._state[i] = 0.0 else: nrm += _np.abs(self._state[i]) ** 2 - self._state *= 1. / _np.sqrt(nrm) + self._state *= 1.0 / _np.sqrt(nrm) return res def allocate_qubit(self, ID): @@ -117,7 +118,7 @@ def allocate_qubit(self, ID): self._num_qubits += 1 self._state.resize(1 << self._num_qubits, refcheck=_USE_REFCHECK) - def get_classical_value(self, ID, tol=1.e-10): + def get_classical_value(self, ID, tol=1.0e-10): """ Return the classical value of a classical bit (i.e., a qubit which has been measured / uncomputed). @@ -141,10 +142,12 @@ def get_classical_value(self, ID, tol=1.e-10): if _np.abs(self._state[i + j + (1 << pos)]) > tol: down = True if up and down: - raise RuntimeError("Qubit has not been measured / " - "uncomputed. Cannot access its " - "classical value and/or deallocate a " - "qubit in superposition!") + raise RuntimeError( + "Qubit has not been measured / " + "uncomputed. Cannot access its " + "classical value and/or deallocate a " + "qubit in superposition!" + ) return down def deallocate_qubit(self, ID): @@ -162,13 +165,11 @@ def deallocate_qubit(self, ID): cv = self.get_classical_value(ID) - newstate = _np.zeros((1 << (self._num_qubits - 1)), - dtype=_np.complex128) + newstate = _np.zeros((1 << (self._num_qubits - 1)), dtype=_np.complex128) k = 0 - for i in range((1 << pos) * int(cv), len(self._state), - (1 << (pos + 1))): - newstate[k:k + (1 << pos)] = self._state[i:i + (1 << pos)] - k += (1 << pos) + for i in range((1 << pos) * int(cv), len(self._state), (1 << (pos + 1))): + newstate[k : k + (1 << pos)] = self._state[i : i + (1 << pos)] # noqa: E203 + k += 1 << pos newmap = dict() for key, value in self._map.items(): @@ -190,7 +191,7 @@ def _get_control_mask(self, ctrlids): mask = 0 for ctrlid in ctrlids: ctrlpos = self._map[ctrlid] - mask |= (1 << ctrlpos) + mask |= 1 << ctrlpos return mask def emulate_math(self, f, qubit_ids, ctrlqubit_ids): @@ -218,16 +219,14 @@ def emulate_math(self, f, qubit_ids, ctrlqubit_ids): arg_list = [0] * len(qb_locs) for qr_i in range(len(qb_locs)): for qb_i in range(len(qb_locs[qr_i])): - arg_list[qr_i] |= (((i >> qb_locs[qr_i][qb_i]) & 1) << - qb_i) + arg_list[qr_i] |= ((i >> qb_locs[qr_i][qb_i]) & 1) << qb_i res = f(arg_list) new_i = i for qr_i in range(len(qb_locs)): for qb_i in range(len(qb_locs[qr_i])): - if not (((new_i >> qb_locs[qr_i][qb_i]) & 1) == - ((res[qr_i] >> qb_i) & 1)): - new_i ^= (1 << qb_locs[qr_i][qb_i]) + if not (((new_i >> qb_locs[qr_i][qb_i]) & 1) == ((res[qr_i] >> qb_i) & 1)): + new_i ^= 1 << qb_locs[qr_i][qb_i] newstate[new_i] = self._state[i] else: newstate[i] = self._state[i] @@ -245,7 +244,7 @@ def get_expectation_value(self, terms_dict, ids): Returns: Expectation value """ - expectation = 0. + expectation = 0.0 current_state = _np.copy(self._state) for (term, coefficient) in terms_dict: self._apply_term(term, ids) @@ -288,19 +287,17 @@ def get_probability(self, bit_string, ids): """ for i in range(len(ids)): if ids[i] not in self._map: - raise RuntimeError("get_probability(): Unknown qubit id. " - "Please make sure you have called " - "eng.flush().") + raise RuntimeError("get_probability(): Unknown qubit id. Please make sure you have called eng.flush().") mask = 0 bit_str = 0 for i in range(len(ids)): - mask |= (1 << self._map[ids[i]]) - bit_str |= (bit_string[i] << self._map[ids[i]]) - probability = 0. + mask |= 1 << self._map[ids[i]] + bit_str |= bit_string[i] << self._map[ids[i]] + probability = 0.0 for i in range(len(self._state)): if (i & mask) == bit_str: e = self._state[i] - probability += e.real**2 + e.imag**2 + probability += e.real ** 2 + e.imag ** 2 return probability def get_amplitude(self, bit_string, ids): @@ -321,13 +318,15 @@ def get_amplitude(self, bit_string, ids): allocated qubits. """ if not set(ids) == set(self._map): - raise RuntimeError("The second argument to get_amplitude() must" - " be a permutation of all allocated qubits. " - "Please make sure you have called " - "eng.flush().") + raise RuntimeError( + "The second argument to get_amplitude() must" + " be a permutation of all allocated qubits. " + "Please make sure you have called " + "eng.flush()." + ) index = 0 for i in range(len(ids)): - index |= (bit_string[i] << self._map[ids[i]]) + index |= bit_string[i] << self._map[ids[i]] return self._state[index] def emulate_time_evolution(self, terms_dict, time, ids, ctrlids): @@ -353,14 +352,14 @@ def emulate_time_evolution(self, terms_dict, time, ids, ctrlids): terms_dict = [(t, c) for (t, c) in terms_dict if len(t) > 0] op_nrm = abs(time) * sum([abs(c) for (_, c) in terms_dict]) # rescale the operator by s: - s = int(op_nrm + 1.) + s = int(op_nrm + 1.0) correction = _np.exp(-1j * time * tr / float(s)) output_state = _np.copy(self._state) mask = self._get_control_mask(ctrlids) for i in range(s): j = 0 - nrm_change = 1. - while nrm_change > 1.e-12: + nrm_change = 1.0 + while nrm_change > 1.0e-12: coeff = (-time * 1j) / float(s * (j + 1)) current_state = _np.copy(self._state) update = 0j @@ -413,6 +412,7 @@ def _single_qubit_gate(self, m, pos, mask): pos (int): Bit-position of the qubit. mask (int): Bit-mask where set bits indicate control qubits. """ + def kernel(u, d, m): return u * m[0][0] + d * m[0][1], u * m[1][0] + d * m[1][1] @@ -421,10 +421,7 @@ def kernel(u, d, m): if ((i + j) & mask) == mask: id1 = i + j id2 = id1 + (1 << pos) - self._state[id1], self._state[id2] = kernel( - self._state[id1], - self._state[id2], - m) + self._state[id1], self._state[id2] = kernel(self._state[id1], self._state[id2], m) def _multi_qubit_gate(self, m, pos, mask): """ @@ -474,11 +471,12 @@ def set_wavefunction(self, wavefunction, ordering): # wavefunction contains 2^n values for n qubits assert len(wavefunction) == (1 << len(ordering)) # all qubits must have been allocated before - if (not all([Id in self._map for Id in ordering]) or - len(self._map) != len(ordering)): - raise RuntimeError("set_wavefunction(): Invalid mapping provided." - " Please make sure all qubits have been " - "allocated previously (call eng.flush()).") + if not all([Id in self._map for Id in ordering]) or len(self._map) != len(ordering): + raise RuntimeError( + "set_wavefunction(): Invalid mapping provided." + " Please make sure all qubits have been " + "allocated previously (call eng.flush())." + ) self._state = _np.array(wavefunction, dtype=_np.complex128) self._map = {ordering[i]: i for i in range(len(ordering))} @@ -498,26 +496,27 @@ def collapse_wavefunction(self, ids, values): assert len(ids) == len(values) # all qubits must have been allocated before if not all([Id in self._map for Id in ids]): - raise RuntimeError("collapse_wavefunction(): Unknown qubit id(s)" - " provided. Try calling eng.flush() before " - "invoking this function.") + raise RuntimeError( + "collapse_wavefunction(): Unknown qubit id(s)" + " provided. Try calling eng.flush() before " + "invoking this function." + ) mask = 0 val = 0 for i in range(len(ids)): pos = self._map[ids[i]] - mask |= (1 << pos) - val |= (int(values[i]) << pos) - nrm = 0. + mask |= 1 << pos + val |= int(values[i]) << pos + nrm = 0.0 for i in range(len(self._state)): if (mask & i) == val: nrm += _np.abs(self._state[i]) ** 2 - if nrm < 1.e-12: - raise RuntimeError("collapse_wavefunction(): Invalid collapse! " - "Probability is ~0.") - inv_nrm = 1. / _np.sqrt(nrm) + if nrm < 1.0e-12: + raise RuntimeError("collapse_wavefunction(): Invalid collapse! Probability is ~0.") + inv_nrm = 1.0 / _np.sqrt(nrm) for i in range(len(self._state)): if (mask & i) != val: - self._state[i] = 0. + self._state[i] = 0.0 else: self._state[i] *= inv_nrm @@ -537,11 +536,10 @@ def _apply_term(self, term, ids, ctrlids=[]): ids (list[int]): Term index to Qubit ID mapping ctrlids (list[int]): Control qubit IDs """ - X = [[0., 1.], [1., 0.]] - Y = [[0., -1j], [1j, 0.]] - Z = [[1., 0.], [0., -1.]] + X = [[0.0, 1.0], [1.0, 0.0]] + Y = [[0.0, -1j], [1j, 0.0]] + Z = [[1.0, 0.0], [0.0, -1.0]] gates = [X, Y, Z] for local_op in term: qb_id = ids[local_op[0]] - self.apply_controlled_gate(gates[ord(local_op[1]) - ord('X')], - [qb_id], ctrlids) + self.apply_controlled_gate(gates[ord(local_op[1]) - ord('X')], [qb_id], ctrlids) diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index 19e884d6d..4aca230f6 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -1,4 +1,5 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# -*- coding: utf-8 -*- +# Copyright 2017, 2021 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. @@ -11,7 +12,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. - """ Contains the projectq interface to a C++-based simulator, which has to be built first. If the c++ simulator is not exported to python, a (slow) python @@ -22,22 +22,22 @@ import random from projectq.cengines import BasicEngine from projectq.meta import get_control_count, LogicalQubitIDTag -from projectq.ops import (NOT, - H, - R, - Measure, - FlushGate, - Allocate, - Deallocate, - BasicMathGate, - TimeEvolution) +from projectq.ops import ( + Measure, + FlushGate, + Allocate, + Deallocate, + BasicMathGate, + TimeEvolution, +) from projectq.types import WeakQubitRef FALLBACK_TO_PYSIM = False try: from ._cppsim import Simulator as SimulatorBackend -except ImportError: +except ImportError: # pragma: no cover from ._pysim import Simulator as SimulatorBackend + FALLBACK_TO_PYSIM = True @@ -54,6 +54,7 @@ class Simulator(BasicEngine): export OMP_NUM_THREADS=4 # use 4 threads export OMP_PROC_BIND=spread # bind threads to processors by spreading """ + def __init__(self, gate_fusion=False, rnd_seed=None): """ Construct the C++/Python-simulator object and initialize it with a @@ -103,10 +104,13 @@ def is_available(self, cmd): Returns: True if it can be simulated and False otherwise. """ - if (cmd.gate == Measure or cmd.gate == Allocate or - cmd.gate == Deallocate or - isinstance(cmd.gate, BasicMathGate) or - isinstance(cmd.gate, TimeEvolution)): + if ( + cmd.gate == Measure + or cmd.gate == Allocate + or cmd.gate == Deallocate + or isinstance(cmd.gate, BasicMathGate) + or isinstance(cmd.gate, TimeEvolution) + ): return True try: m = cmd.gate.matrix @@ -114,7 +118,7 @@ def is_available(self, cmd): if len(m) > 2 ** 5: return False return True - except: + except AttributeError: return False def _convert_logical_to_mapped_qureg(self, qureg): @@ -129,11 +133,8 @@ def _convert_logical_to_mapped_qureg(self, qureg): mapped_qureg = [] for qubit in qureg: if qubit.id not in mapper.current_mapping: - raise RuntimeError("Unknown qubit id. " - "Please make sure you have called " - "eng.flush().") - new_qubit = WeakQubitRef(qubit.engine, - mapper.current_mapping[qubit.id]) + raise RuntimeError("Unknown qubit id. Please make sure you have called eng.flush().") + new_qubit = WeakQubitRef(qubit.engine, mapper.current_mapping[qubit.id]) mapped_qureg.append(new_qubit) return mapped_qureg else: @@ -169,12 +170,9 @@ def get_expectation_value(self, qubit_operator, qureg): num_qubits = len(qureg) for term, _ in qubit_operator.terms.items(): if not term == () and term[-1][0] >= num_qubits: - raise Exception("qubit_operator acts on more qubits than " - "contained in the qureg.") - operator = [(list(term), coeff) for (term, coeff) - in qubit_operator.terms.items()] - return self._simulator.get_expectation_value(operator, - [qb.id for qb in qureg]) + raise Exception("qubit_operator acts on more qubits than contained in the qureg.") + operator = [(list(term), coeff) for (term, coeff) in qubit_operator.terms.items()] + return self._simulator.get_expectation_value(operator, [qb.id for qb in qureg]) def apply_qubit_operator(self, qubit_operator, qureg): """ @@ -209,12 +207,9 @@ def apply_qubit_operator(self, qubit_operator, qureg): num_qubits = len(qureg) for term, _ in qubit_operator.terms.items(): if not term == () and term[-1][0] >= num_qubits: - raise Exception("qubit_operator acts on more qubits than " - "contained in the qureg.") - operator = [(list(term), coeff) for (term, coeff) - in qubit_operator.terms.items()] - return self._simulator.apply_qubit_operator(operator, - [qb.id for qb in qureg]) + raise Exception("qubit_operator acts on more qubits than contained in the qureg.") + operator = [(list(term), coeff) for (term, coeff) in qubit_operator.terms.items()] + return self._simulator.apply_qubit_operator(operator, [qb.id for qb in qureg]) def get_probability(self, bit_string, qureg): """ @@ -240,8 +235,7 @@ def get_probability(self, bit_string, qureg): """ qureg = self._convert_logical_to_mapped_qureg(qureg) bit_string = [bool(int(b)) for b in bit_string] - return self._simulator.get_probability(bit_string, - [qb.id for qb in qureg]) + return self._simulator.get_probability(bit_string, [qb.id for qb in qureg]) def get_amplitude(self, bit_string, qureg): """ @@ -269,8 +263,7 @@ def get_amplitude(self, bit_string, qureg): """ qureg = self._convert_logical_to_mapped_qureg(qureg) bit_string = [bool(int(b)) for b in bit_string] - return self._simulator.get_amplitude(bit_string, - [qb.id for qb in qureg]) + return self._simulator.get_amplitude(bit_string, [qb.id for qb in qureg]) def set_wavefunction(self, wavefunction, qureg): """ @@ -296,8 +289,7 @@ def set_wavefunction(self, wavefunction, qureg): the qureg argument. """ qureg = self._convert_logical_to_mapped_qureg(qureg) - self._simulator.set_wavefunction(wavefunction, - [qb.id for qb in qureg]) + self._simulator.set_wavefunction(wavefunction, [qb.id for qb in qureg]) def collapse_wavefunction(self, qureg, values): """ @@ -322,9 +314,7 @@ def collapse_wavefunction(self, qureg, values): the qureg argument. """ qureg = self._convert_logical_to_mapped_qureg(qureg) - return self._simulator.collapse_wavefunction([qb.id for qb in qureg], - [bool(int(v)) for v in - values]) + return self._simulator.collapse_wavefunction([qb.id for qb in qureg], [bool(int(v)) for v in values]) def cheat(self): """ @@ -363,7 +353,7 @@ def _handle(self, cmd): (which should never happen due to is_available). """ if cmd.gate == Measure: - assert(get_control_count(cmd) == 0) + assert get_control_count(cmd) == 0 ids = [qb.id for qr in cmd.qubits for qb in qr] out = self._simulator.measure_qubits(ids) i = 0 @@ -375,8 +365,7 @@ def _handle(self, cmd): if isinstance(tag, LogicalQubitIDTag): logical_id_tag = tag if logical_id_tag is not None: - qb = WeakQubitRef(qb.engine, - logical_id_tag.logical_qubit_id) + qb = WeakQubitRef(qb.engine, logical_id_tag.logical_qubit_id) self.main_engine.set_measurement_result(qb, out[i]) i += 1 elif cmd.gate == Allocate: @@ -387,9 +376,12 @@ def _handle(self, cmd): self._simulator.deallocate_qubit(ID) elif isinstance(cmd.gate, BasicMathGate): # improve performance by using C++ code for some commomn gates - from projectq.libs.math import (AddConstant, - AddConstantModN, - MultiplyByConstantModN) + from projectq.libs.math import ( + AddConstant, + AddConstantModN, + MultiplyByConstantModN, + ) + qubitids = [] for qr in cmd.qubits: qubitids.append([]) @@ -397,26 +389,30 @@ def _handle(self, cmd): qubitids[-1].append(qb.id) if FALLBACK_TO_PYSIM: math_fun = cmd.gate.get_math_function(cmd.qubits) - self._simulator.emulate_math(math_fun, qubitids, - [qb.id for qb in cmd.control_qubits]) + self._simulator.emulate_math(math_fun, qubitids, [qb.id for qb in cmd.control_qubits]) else: # individual code for different standard gates to make it faster! if isinstance(cmd.gate, AddConstant): - self._simulator.emulate_math_addConstant(cmd.gate.a, qubitids, - [qb.id for qb in cmd.control_qubits]) + self._simulator.emulate_math_addConstant(cmd.gate.a, qubitids, [qb.id for qb in cmd.control_qubits]) elif isinstance(cmd.gate, AddConstantModN): - self._simulator.emulate_math_addConstantModN(cmd.gate.a, cmd.gate.N, qubitids, - [qb.id for qb in cmd.control_qubits]) + self._simulator.emulate_math_addConstantModN( + cmd.gate.a, + cmd.gate.N, + qubitids, + [qb.id for qb in cmd.control_qubits], + ) elif isinstance(cmd.gate, MultiplyByConstantModN): - self._simulator.emulate_math_multiplyByConstantModN(cmd.gate.a, cmd.gate.N, qubitids, - [qb.id for qb in cmd.control_qubits]) + self._simulator.emulate_math_multiplyByConstantModN( + cmd.gate.a, + cmd.gate.N, + qubitids, + [qb.id for qb in cmd.control_qubits], + ) else: math_fun = cmd.gate.get_math_function(cmd.qubits) - self._simulator.emulate_math(math_fun, qubitids, - [qb.id for qb in cmd.control_qubits]) + self._simulator.emulate_math(math_fun, qubitids, [qb.id for qb in cmd.control_qubits]) elif isinstance(cmd.gate, TimeEvolution): - op = [(list(term), coeff) for (term, coeff) - in cmd.gate.hamiltonian.terms.items()] + op = [(list(term), coeff) for (term, coeff) in cmd.gate.hamiltonian.terms.items()] t = cmd.gate.time qubitids = [qb.id for qb in cmd.qubits[0]] ctrlids = [qb.id for qb in cmd.control_qubits] @@ -425,21 +421,21 @@ def _handle(self, cmd): matrix = cmd.gate.matrix ids = [qb.id for qr in cmd.qubits for qb in qr] if not 2 ** len(ids) == len(cmd.gate.matrix): - raise Exception("Simulator: Error applying {} gate: " - "{}-qubit gate applied to {} qubits.".format( - str(cmd.gate), - int(math.log(len(cmd.gate.matrix), 2)), - len(ids))) - self._simulator.apply_controlled_gate(matrix.tolist(), - ids, - [qb.id for qb in - cmd.control_qubits]) + raise Exception( + "Simulator: Error applying {} gate: " + "{}-qubit gate applied to {} qubits.".format( + str(cmd.gate), int(math.log(len(cmd.gate.matrix), 2)), len(ids) + ) + ) + self._simulator.apply_controlled_gate(matrix.tolist(), ids, [qb.id for qb in cmd.control_qubits]) if not self._gate_fusion: self._simulator.run() else: - raise Exception("This simulator only supports controlled k-qubit" - " gates with k < 6!\nPlease add an auto-replacer" - " engine to your list of compiler engines.") + raise Exception( + "This simulator only supports controlled k-qubit" + " gates with k < 6!\nPlease add an auto-replacer" + " engine to your list of compiler engines." + ) def receive(self, command_list): """ diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 9f7d298cb..0b7f6d288 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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._sim._simulator.py, using both the Python and the C++ simulator as backends. @@ -27,11 +27,34 @@ import scipy.sparse.linalg from projectq import MainEngine -from projectq.cengines import (BasicEngine, BasicMapperEngine, DummyEngine, - LocalOptimizer, NotYetMeasuredError) -from projectq.ops import (All, Allocate, BasicGate, BasicMathGate, CNOT, - Command, H, MatrixGate, Measure, QubitOperator, - Rx, Ry, Rz, S, TimeEvolution, Toffoli, X, Y, Z) +from projectq.cengines import ( + BasicEngine, + BasicMapperEngine, + DummyEngine, + LocalOptimizer, + NotYetMeasuredError, +) +from projectq.ops import ( + All, + Allocate, + BasicGate, + BasicMathGate, + CNOT, + Command, + H, + MatrixGate, + Measure, + QubitOperator, + Rx, + Ry, + Rz, + S, + TimeEvolution, + Toffoli, + X, + Y, + Z, +) from projectq.meta import Control, Dagger, LogicalQubitIDTag from projectq.types import WeakQubitRef @@ -40,13 +63,15 @@ def test_is_cpp_simulator_present(): import projectq.backends._sim._cppsim + assert projectq.backends._sim._cppsim def get_available_simulators(): result = ["py_simulator"] try: - import projectq.backends._sim._cppsim as _ + import projectq.backends._sim._cppsim # noqa: F401 + result.append("cpp_simulator") except ImportError: # The C++ simulator was either not installed or is misconfigured. Skip. @@ -58,11 +83,13 @@ def get_available_simulators(): def sim(request): if request.param == "cpp_simulator": from projectq.backends._sim._cppsim import Simulator as CppSim + sim = Simulator(gate_fusion=True) sim._simulator = CppSim(1) return sim if request.param == "py_simulator": from projectq.backends._sim._pysim import Simulator as PySim + sim = Simulator() sim._simulator = PySim(1) return sim @@ -169,7 +196,7 @@ def test_simulator_cheat(sim): assert len(sim.cheat()[0]) == 1 assert sim.cheat()[0][0] == 0 assert len(sim.cheat()[1]) == 2 - assert 1. == pytest.approx(abs(sim.cheat()[1][0])) + assert 1.0 == pytest.approx(abs(sim.cheat()[1][0])) qubit[0].__del__() # should be empty: @@ -198,8 +225,13 @@ def test_simulator_measure_mapped_qubit(sim): qb2 = WeakQubitRef(engine=eng, idx=2) cmd0 = Command(engine=eng, gate=Allocate, qubits=([qb1],)) cmd1 = Command(engine=eng, gate=X, qubits=([qb1],)) - cmd2 = Command(engine=eng, gate=Measure, qubits=([qb1],), controls=[], - tags=[LogicalQubitIDTag(2)]) + cmd2 = Command( + engine=eng, + gate=Measure, + qubits=([qb1],), + controls=[], + tags=[LogicalQubitIDTag(2)], + ) with pytest.raises(NotYetMeasuredError): int(qb1) with pytest.raises(NotYetMeasuredError): @@ -213,7 +245,7 @@ def test_simulator_measure_mapped_qubit(sim): class Plus2Gate(BasicMathGate): def __init__(self): - BasicMathGate.__init__(self, lambda x: (x+2,)) + BasicMathGate.__init__(self, lambda x: (x + 2,)) def test_simulator_emulation(sim): @@ -225,12 +257,12 @@ def test_simulator_emulation(sim): with Control(eng, qubit3): Plus2Gate() | (qubit1 + qubit2) - assert 1. == pytest.approx(sim.cheat()[1][0]) + assert 1.0 == pytest.approx(sim.cheat()[1][0]) X | qubit3 with Control(eng, qubit3): Plus2Gate() | (qubit1 + qubit2) - assert 1. == pytest.approx(sim.cheat()[1][6]) + assert 1.0 == pytest.approx(sim.cheat()[1][6]) All(Measure) | (qubit1 + qubit2 + qubit3) @@ -262,7 +294,7 @@ def matrix(self): with Control(eng, qubit): with Dagger(eng): KQubitGate() | qureg - assert sim.get_amplitude('0' * 5, qubit + qureg) == pytest.approx(1.) + assert sim.get_amplitude('0' * 5, qubit + qureg) == pytest.approx(1.0) class LargerGate(BasicGate): @property @@ -303,8 +335,7 @@ def test_simulator_probability(sim, mapper): eng.flush() bits = [0, 0, 1, 0, 1, 0] for i in range(6): - assert (eng.backend.get_probability(bits[:i], qubits[:i]) == - pytest.approx(0.5**i)) + assert eng.backend.get_probability(bits[:i], qubits[:i]) == pytest.approx(0.5 ** i) extra_qubit = eng.allocate_qubit() with pytest.raises(RuntimeError): eng.backend.get_probability([0], extra_qubit) @@ -316,12 +347,9 @@ def test_simulator_probability(sim, mapper): Ry(2 * math.acos(math.sqrt(0.4))) | qubits[2] eng.flush() assert eng.backend.get_probability([0], [qubits[2]]) == pytest.approx(0.4) - assert (eng.backend.get_probability([0, 0], qubits[:3:2]) == - pytest.approx(0.12)) - assert (eng.backend.get_probability([0, 1], qubits[:3:2]) == - pytest.approx(0.18)) - assert (eng.backend.get_probability([1, 0], qubits[:3:2]) == - pytest.approx(0.28)) + assert eng.backend.get_probability([0, 0], qubits[:3:2]) == pytest.approx(0.12) + assert eng.backend.get_probability([0, 1], qubits[:3:2]) == pytest.approx(0.18) + assert eng.backend.get_probability([1, 0], qubits[:3:2]) == pytest.approx(0.28) All(Measure) | qubits @@ -335,11 +363,11 @@ def test_simulator_amplitude(sim, mapper): All(H) | qubits eng.flush() bits = [0, 0, 1, 0, 1, 0] - assert eng.backend.get_amplitude(bits, qubits) == pytest.approx(1. / 8.) + assert eng.backend.get_amplitude(bits, qubits) == pytest.approx(1.0 / 8.0) bits = [0, 0, 0, 0, 1, 0] - assert eng.backend.get_amplitude(bits, qubits) == pytest.approx(-1. / 8.) + assert eng.backend.get_amplitude(bits, qubits) == pytest.approx(-1.0 / 8.0) bits = [0, 1, 1, 0, 1, 0] - assert eng.backend.get_amplitude(bits, qubits) == pytest.approx(-1. / 8.) + assert eng.backend.get_amplitude(bits, qubits) == pytest.approx(-1.0 / 8.0) All(H) | qubits All(X) | qubits Ry(2 * math.acos(0.3)) | qubits[0] @@ -347,8 +375,7 @@ def test_simulator_amplitude(sim, mapper): bits = [0] * 6 assert eng.backend.get_amplitude(bits, qubits) == pytest.approx(0.3) bits[0] = 1 - assert (eng.backend.get_amplitude(bits, qubits) == - pytest.approx(math.sqrt(0.91))) + assert eng.backend.get_amplitude(bits, qubits) == pytest.approx(math.sqrt(0.91)) All(Measure) | qubits # raises if not all qubits are in the list: with pytest.raises(RuntimeError): @@ -356,7 +383,7 @@ def test_simulator_amplitude(sim, mapper): # doesn't just check for length: with pytest.raises(RuntimeError): eng.backend.get_amplitude(bits, qubits[:-1] + [qubits[0]]) - extra_qubit = eng.allocate_qubit() + extra_qubit = eng.allocate_qubit() # noqa: F841 eng.flush() # there is a new qubit now! with pytest.raises(RuntimeError): @@ -371,42 +398,44 @@ def test_simulator_expectation(sim, mapper): qureg = eng.allocate_qureg(3) op0 = QubitOperator('Z0') expectation = sim.get_expectation_value(op0, qureg) - assert 1. == pytest.approx(expectation) + assert 1.0 == pytest.approx(expectation) X | qureg[0] expectation = sim.get_expectation_value(op0, qureg) - assert -1. == pytest.approx(expectation) + assert -1.0 == pytest.approx(expectation) H | qureg[0] op1 = QubitOperator('X0') expectation = sim.get_expectation_value(op1, qureg) - assert -1. == pytest.approx(expectation) + assert -1.0 == pytest.approx(expectation) Z | qureg[0] expectation = sim.get_expectation_value(op1, qureg) - assert 1. == pytest.approx(expectation) + assert 1.0 == pytest.approx(expectation) X | qureg[0] S | qureg[0] Z | qureg[0] X | qureg[0] op2 = QubitOperator('Y0') expectation = sim.get_expectation_value(op2, qureg) - assert 1. == pytest.approx(expectation) + assert 1.0 == pytest.approx(expectation) Z | qureg[0] expectation = sim.get_expectation_value(op2, qureg) - assert -1. == pytest.approx(expectation) + assert -1.0 == pytest.approx(expectation) op_sum = QubitOperator('Y0 X1 Z2') + QubitOperator('X1') H | qureg[1] X | qureg[2] expectation = sim.get_expectation_value(op_sum, qureg) - assert 2. == pytest.approx(expectation) + assert 2.0 == pytest.approx(expectation) op_sum = QubitOperator('Y0 X1 Z2') + QubitOperator('X1') X | qureg[2] expectation = sim.get_expectation_value(op_sum, qureg) - assert 0. == pytest.approx(expectation) + assert 0.0 == pytest.approx(expectation) - op_id = .4 * QubitOperator(()) + op_id = 0.4 * QubitOperator(()) expectation = sim.get_expectation_value(op_id, qureg) - assert .4 == pytest.approx(expectation) + assert 0.4 == pytest.approx(expectation) + + All(Measure) | qureg def test_simulator_expectation_exception(sim): @@ -439,27 +468,27 @@ def test_simulator_applyqubitoperator(sim, mapper): engine_list = [] if mapper is not None: engine_list.append(mapper) - eng = MainEngine(sim, engine_list=engine_list) + eng = MainEngine(sim, engine_list=engine_list, verbose=True) qureg = eng.allocate_qureg(3) op = QubitOperator('X0 Y1 Z2') sim.apply_qubit_operator(op, qureg) X | qureg[0] Y | qureg[1] Z | qureg[2] - assert sim.get_amplitude('000', qureg) == pytest.approx(1.) + assert sim.get_amplitude('000', qureg) == pytest.approx(1.0) H | qureg[0] - op_H = 1. / math.sqrt(2.) * (QubitOperator('X0') + QubitOperator('Z0')) + op_H = 1.0 / math.sqrt(2.0) * (QubitOperator('X0') + QubitOperator('Z0')) sim.apply_qubit_operator(op_H, [qureg[0]]) - assert sim.get_amplitude('000', qureg) == pytest.approx(1.) + assert sim.get_amplitude('000', qureg) == pytest.approx(1.0) op_Proj0 = 0.5 * (QubitOperator('') + QubitOperator('Z0')) op_Proj1 = 0.5 * (QubitOperator('') - QubitOperator('Z0')) H | qureg[0] sim.apply_qubit_operator(op_Proj0, [qureg[0]]) - assert sim.get_amplitude('000', qureg) == pytest.approx(1. / math.sqrt(2.)) + assert sim.get_amplitude('000', qureg) == pytest.approx(1.0 / math.sqrt(2.0)) sim.apply_qubit_operator(op_Proj1, [qureg[0]]) - assert sim.get_amplitude('000', qureg) == pytest.approx(0.) + assert sim.get_amplitude('000', qureg) == pytest.approx(0.0) def test_simulator_time_evolution(sim): @@ -486,6 +515,7 @@ def test_simulator_time_evolution(sim): eng.flush() qbit_to_bit_map, final_wavefunction = copy.deepcopy(eng.backend.cheat()) All(Measure) | qureg + ctrl_qubit + # Check manually: def build_matrix(list_single_matrices): @@ -493,18 +523,18 @@ def build_matrix(list_single_matrices): for i in range(1, len(list_single_matrices)): res = scipy.sparse.kron(res, list_single_matrices[i]) return res + id_sp = scipy.sparse.identity(2, format="csr", dtype=complex) - x_sp = scipy.sparse.csr_matrix([[0., 1.], [1., 0.]], dtype=complex) - y_sp = scipy.sparse.csr_matrix([[0., -1.j], [1.j, 0.]], dtype=complex) - z_sp = scipy.sparse.csr_matrix([[1., 0.], [0., -1.]], dtype=complex) + x_sp = scipy.sparse.csr_matrix([[0.0, 1.0], [1.0, 0.0]], dtype=complex) + y_sp = scipy.sparse.csr_matrix([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) + z_sp = scipy.sparse.csr_matrix([[1.0, 0.0], [0.0, -1.0]], dtype=complex) gates = [x_sp, y_sp, z_sp] res_matrix = 0 for t, c in op.terms.items(): matrix = [id_sp] * N for idx, gate in t: - matrix[qbit_to_bit_map[qureg[idx].id]] = gates[ord(gate) - - ord('X')] + matrix[qbit_to_bit_map[qureg[idx].id]] = gates[ord(gate) - ord('X')] matrix.reverse() res_matrix += build_matrix(matrix) * c res_matrix *= -1j * time_to_evolve @@ -514,11 +544,10 @@ def build_matrix(list_single_matrices): res = scipy.sparse.linalg.expm_multiply(res_matrix, init_wavefunction) half = int(len(final_wavefunction) / 2) - hadamard_f = 1. / math.sqrt(2.) + hadamard_f = 1.0 / math.sqrt(2.0) # check evolution and control assert numpy.allclose(hadamard_f * res, final_wavefunction[half:]) - assert numpy.allclose(final_wavefunction[:half], hadamard_f * - init_wavefunction) + assert numpy.allclose(final_wavefunction[:half], hadamard_f * init_wavefunction) def test_simulator_set_wavefunction(sim, mapper): @@ -527,23 +556,23 @@ def test_simulator_set_wavefunction(sim, mapper): engine_list.append(mapper) eng = MainEngine(sim, engine_list=engine_list) qubits = eng.allocate_qureg(2) - wf = [0., 0., math.sqrt(0.2), math.sqrt(0.8)] + wf = [0.0, 0.0, math.sqrt(0.2), math.sqrt(0.8)] with pytest.raises(RuntimeError): eng.backend.set_wavefunction(wf, qubits) eng.flush() eng.backend.set_wavefunction(wf, qubits) - assert pytest.approx(eng.backend.get_probability('1', [qubits[0]])) == .8 - assert pytest.approx(eng.backend.get_probability('01', qubits)) == .2 - assert pytest.approx(eng.backend.get_probability('1', [qubits[1]])) == 1. + assert pytest.approx(eng.backend.get_probability('1', [qubits[0]])) == 0.8 + assert pytest.approx(eng.backend.get_probability('01', qubits)) == 0.2 + assert pytest.approx(eng.backend.get_probability('1', [qubits[1]])) == 1.0 All(Measure) | qubits def test_simulator_set_wavefunction_always_complex(sim): - """ Checks that wavefunction is always complex """ + """Checks that wavefunction is always complex""" eng = MainEngine(sim) qubit = eng.allocate_qubit() eng.flush() - wf = [1., 0] + wf = [1.0, 0] eng.backend.set_wavefunction(wf, qubit) Y | qubit eng.flush() @@ -561,23 +590,23 @@ def test_simulator_collapse_wavefunction(sim, mapper): eng.backend.collapse_wavefunction(qubits, [0] * 4) eng.flush() eng.backend.collapse_wavefunction(qubits, [0] * 4) - assert pytest.approx(eng.backend.get_probability([0] * 4, qubits)) == 1. + assert pytest.approx(eng.backend.get_probability([0] * 4, qubits)) == 1.0 All(H) | qubits[1:] eng.flush() - assert pytest.approx(eng.backend.get_probability([0] * 4, qubits)) == .125 + assert pytest.approx(eng.backend.get_probability([0] * 4, qubits)) == 0.125 # impossible outcome: raises with pytest.raises(RuntimeError): eng.backend.collapse_wavefunction(qubits, [1] + [0] * 3) eng.backend.collapse_wavefunction(qubits[:-1], [0, 1, 0]) probability = eng.backend.get_probability([0, 1, 0, 1], qubits) - assert probability == pytest.approx(.5) - eng.backend.set_wavefunction([1.] + [0.] * 15, qubits) + assert probability == pytest.approx(0.5) + eng.backend.set_wavefunction([1.0] + [0.0] * 15, qubits) H | qubits[0] CNOT | (qubits[0], qubits[1]) eng.flush() eng.backend.collapse_wavefunction([qubits[0]], [1]) probability = eng.backend.get_probability([1, 1], qubits[0:2]) - assert probability == pytest.approx(1.) + assert probability == pytest.approx(1.0) def test_simulator_no_uncompute_exception(sim): @@ -632,10 +661,10 @@ def test_simulator_functional_entangle(sim): CNOT | (qubits[0], qb) # check the state vector: - assert .5 == pytest.approx(abs(sim.cheat()[1][0])**2) - assert .5 == pytest.approx(abs(sim.cheat()[1][31])**2) + assert 0.5 == pytest.approx(abs(sim.cheat()[1][0]) ** 2) + assert 0.5 == pytest.approx(abs(sim.cheat()[1][31]) ** 2) for i in range(1, 31): - assert 0. == pytest.approx(abs(sim.cheat()[1][i])) + assert 0.0 == pytest.approx(abs(sim.cheat()[1][i])) # unentangle all except the first 2 for qb in qubits[2:]: @@ -646,10 +675,10 @@ def test_simulator_functional_entangle(sim): Toffoli | (qubits[0], qubits[1], qb) # check the state vector: - assert .5 == pytest.approx(abs(sim.cheat()[1][0])**2) - assert .5 == pytest.approx(abs(sim.cheat()[1][31])**2) + assert 0.5 == pytest.approx(abs(sim.cheat()[1][0]) ** 2) + assert 0.5 == pytest.approx(abs(sim.cheat()[1][31]) ** 2) for i in range(1, 31): - assert 0. == pytest.approx(abs(sim.cheat()[1][i])) + assert 0.0 == pytest.approx(abs(sim.cheat()[1][i])) # uncompute using multi-controlled NOTs with Control(eng, qubits[0:-1]): @@ -662,9 +691,9 @@ def test_simulator_functional_entangle(sim): H | qubits[0] # check the state vector: - assert 1. == pytest.approx(abs(sim.cheat()[1][0])**2) + assert 1.0 == pytest.approx(abs(sim.cheat()[1][0]) ** 2) for i in range(1, 32): - assert 0. == pytest.approx(abs(sim.cheat()[1][i])) + assert 0.0 == pytest.approx(abs(sim.cheat()[1][i])) All(Measure) | qubits @@ -679,10 +708,8 @@ def receive(command_list): eng = MainEngine(sim, [mapper]) qubit0 = eng.allocate_qubit() qubit1 = eng.allocate_qubit() - mapper.current_mapping = {qubit0[0].id: qubit1[0].id, - qubit1[0].id: qubit0[0].id} - assert (sim._convert_logical_to_mapped_qureg(qubit0 + qubit1) == - qubit1 + qubit0) + mapper.current_mapping = {qubit0[0].id: qubit1[0].id, qubit1[0].id: qubit0[0].id} + assert sim._convert_logical_to_mapped_qureg(qubit0 + qubit1) == qubit1 + qubit0 def test_simulator_constant_math_emulation(): @@ -695,8 +722,7 @@ def test_simulator_constant_math_emulation(): import projectq.backends._sim._simulator as _sim from projectq.backends._sim._pysim import Simulator as PySim from projectq.backends._sim._cppsim import Simulator as CppSim - from projectq.libs.math import (AddConstant, AddConstantModN, - MultiplyByConstantModN) + from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN def gate_filter(eng, cmd): g = cmd.gate diff --git a/projectq/backends/_sim/_simulator_test_fixtures.py b/projectq/backends/_sim/_simulator_test_fixtures.py new file mode 100644 index 000000000..28cb00f5c --- /dev/null +++ b/projectq/backends/_sim/_simulator_test_fixtures.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from projectq.cengines import BasicEngine, BasicMapperEngine + + +@pytest.fixture(params=["mapper", "no_mapper"]) +def mapper(request): + """ + Adds a mapper which changes qubit ids by adding 1 + """ + if request.param == "mapper": + + class TrivialMapper(BasicMapperEngine): + def __init__(self): + BasicEngine.__init__(self) + self.current_mapping = dict() + + def receive(self, command_list): + for cmd in command_list: + for qureg in cmd.all_qubits: + for qubit in qureg: + if qubit.id == -1: + continue + elif qubit.id not in self.current_mapping: + previous_map = self.current_mapping + previous_map[qubit.id] = qubit.id + 1 + self.current_mapping = previous_map + self._send_cmd_with_mapped_ids(cmd) + + return TrivialMapper() + if request.param == "no_mapper": + return None diff --git a/projectq/cengines/__init__.py b/projectq/cengines/__init__.py index 966159e78..d81b59cee 100755 --- a/projectq/cengines/__init__.py +++ b/projectq/cengines/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,23 +13,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ._basics import (BasicEngine, - LastEngineException, - ForwarderEngine) +from ._basics import BasicEngine, LastEngineException, ForwarderEngine from ._cmdmodifier import CommandModifier from ._basicmapper import BasicMapperEngine from ._ibm5qubitmapper import IBM5QubitMapper from ._swapandcnotflipper import SwapAndCNOTFlipper from ._linearmapper import LinearMapper, return_swap_depth from ._manualmapper import ManualMapper -from ._main import (MainEngine, - NotYetMeasuredError, - UnsupportedEngineError) +from ._main import MainEngine, NotYetMeasuredError, UnsupportedEngineError from ._optimize import LocalOptimizer -from ._replacer import (AutoReplacer, - InstructionFilter, - DecompositionRuleSet, - DecompositionRule) +from ._replacer import ( + AutoReplacer, + InstructionFilter, + DecompositionRuleSet, + DecompositionRule, +) from ._tagremover import TagRemover from ._testengine import CompareEngine, DummyEngine from ._twodmapper import GridMapper diff --git a/projectq/cengines/_basicmapper.py b/projectq/cengines/_basicmapper.py index 5fc0f9a81..0a70b9b26 100644 --- a/projectq/cengines/_basicmapper.py +++ b/projectq/cengines/_basicmapper.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Defines the parent class from which all mappers should be derived. @@ -71,8 +71,7 @@ def _send_cmd_with_mapped_ids(self, cmd): # Add LogicalQubitIDTag to MeasureGate def add_logical_id(command, old_tags=deepcopy(cmd.tags)): - command.tags = (old_tags + - [LogicalQubitIDTag(cmd.qubits[0][0].id)]) + command.tags = old_tags + [LogicalQubitIDTag(cmd.qubits[0][0].id)] return command tagger_eng = CommandModifier(add_logical_id) @@ -81,7 +80,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/_basicmapper_test.py b/projectq/cengines/_basicmapper_test.py index 9a15bf2e1..9a7089e60 100644 --- a/projectq/cengines/_basicmapper_test.py +++ b/projectq/cengines/_basicmapper_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,15 +12,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. - """Tests for projectq.cengines._basicmapper.py.""" -from copy import deepcopy - from projectq.cengines import DummyEngine from projectq.meta import LogicalQubitIDTag -from projectq.ops import (Allocate, BasicGate, Command, Deallocate, FlushGate, - Measure) +from projectq.ops import Allocate, BasicGate, Command, Deallocate, FlushGate, Measure from projectq.types import WeakQubitRef from projectq.cengines import _basicmapper @@ -36,14 +33,16 @@ def test_basic_mapper_engine_send_cmd_with_mapped_ids(): qb1 = WeakQubitRef(engine=None, idx=1) qb2 = WeakQubitRef(engine=None, idx=2) qb3 = WeakQubitRef(engine=None, idx=3) - cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], - tags=[]) - cmd1 = Command(engine=None, gate=Deallocate, qubits=([qb1],), controls=[], - tags=[]) - cmd2 = Command(engine=None, gate=Measure, qubits=([qb2],), controls=[], - tags=["SomeTag"]) - cmd3 = Command(engine=None, gate=BasicGate(), qubits=([qb0, qb1], [qb2]), - controls=[qb3], tags=[]) + cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], tags=[]) + cmd1 = Command(engine=None, gate=Deallocate, qubits=([qb1],), controls=[], tags=[]) + cmd2 = Command(engine=None, gate=Measure, qubits=([qb2],), controls=[], tags=["SomeTag"]) + cmd3 = Command( + engine=None, + gate=BasicGate(), + qubits=([qb0, qb1], [qb2]), + controls=[qb3], + tags=[], + ) cmd4 = Command(None, FlushGate(), ([WeakQubitRef(None, -1)],)) mapper._send_cmd_with_mapped_ids(cmd0) mapper._send_cmd_with_mapped_ids(cmd1) diff --git a/projectq/cengines/_basics.py b/projectq/cengines/_basics.py index 1851d7259..6a6f6c79d 100755 --- a/projectq/cengines/_basics.py +++ b/projectq/cengines/_basics.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,9 +14,8 @@ # limitations under the License. from projectq.ops import Allocate, Deallocate -from projectq.types import Qubit, Qureg +from projectq.types import Qubit, Qureg, WeakQubitRef from projectq.ops import Command -import projectq.cengines class LastEngineException(Exception): @@ -27,12 +27,17 @@ class LastEngineException(Exception): whether the command is available. An engine which legally may be the last engine, this behavior needs to be adapted (see BasicEngine.isAvailable). """ + def __init__(self, engine): - Exception.__init__(self, ("\nERROR: Sending to next engine failed. " - "{} as last engine?\nIf this is legal, " - "please override 'isAvailable' to adapt its" - " behavior." - ).format(engine.__class__.__name__)) + Exception.__init__( + self, + ( + "\nERROR: Sending to next engine failed. " + "{} as last engine?\nIf this is legal, " + "please override 'isAvailable' to adapt its" + " behavior." + ).format(engine.__class__.__name__), + ) class BasicEngine(object): @@ -50,6 +55,7 @@ class BasicEngine(object): main_engine (MainEngine): Reference to the main compiler engine. is_last_engine (bool): True for the last engine, which is the back-end. """ + def __init__(self): """ Initialize the basic engine. @@ -112,6 +118,7 @@ def allocate_qubit(self, dirty=False): cmd = Command(self, Allocate, (qb,)) if dirty: from projectq.meta import DirtyQubitTag + if self.is_meta_tag_supported(DirtyQubitTag): cmd.tags += [DirtyQubitTag()] self.main_engine.dirty_qubits.add(qb[0].id) @@ -146,11 +153,20 @@ def deallocate_qubit(self, qubit): raise ValueError("Already deallocated.") from projectq.meta import DirtyQubitTag + is_dirty = qubit.id in self.main_engine.dirty_qubits - self.send([Command(self, - Deallocate, - (Qureg([qubit]),), - tags=[DirtyQubitTag()] if is_dirty else [])]) + self.send( + [ + Command( + self, + Deallocate, + ([WeakQubitRef(engine=qubit.engine, idx=qubit.id)],), + tags=[DirtyQubitTag()] if is_dirty else [], + ) + ] + ) + # Mark qubit as deallocated + qubit.id = -1 def is_meta_tag_supported(self, meta_tag): """ @@ -167,16 +183,14 @@ def is_meta_tag_supported(self, meta_tag): returns True. """ engine = self - try: - while True: - try: - if engine.is_meta_tag_handler(meta_tag): - return True - except AttributeError: - pass - engine = engine.next_engine - except: - return False + while engine is not None: + try: + if engine.is_meta_tag_handler(meta_tag): + return True + except AttributeError: + pass + engine = engine.next_engine + return False def send(self, command_list): """ @@ -193,6 +207,7 @@ class ForwarderEngine(BasicEngine): It is mainly used as a substitute for the MainEngine at lower levels such that meta operations still work (e.g., with Compute). """ + def __init__(self, engine, cmd_mod_fun=None): """ Initialize a ForwarderEngine. @@ -207,12 +222,13 @@ def __init__(self, engine, cmd_mod_fun=None): self.main_engine = engine.main_engine self.next_engine = engine if cmd_mod_fun is None: + def cmd_mod_fun(cmd): return cmd self._cmd_mod_fun = cmd_mod_fun def receive(self, command_list): - """ Forward all commands to the next engine. """ + """Forward all commands to the next engine.""" new_command_list = [self._cmd_mod_fun(cmd) for cmd in command_list] self.send(new_command_list) diff --git a/projectq/cengines/_basics_test.py b/projectq/cengines/_basics_test.py index 2984e631b..e76b94b7f 100755 --- a/projectq/cengines/_basics_test.py +++ b/projectq/cengines/_basics_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,11 +12,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. - """Tests for projectq.cengines._basics.py.""" import types import pytest + # try: # import mock # except ImportError: @@ -25,10 +26,13 @@ from projectq.types import Qubit from projectq.cengines import DummyEngine, InstructionFilter from projectq.meta import DirtyQubitTag -from projectq.ops import (AllocateQubitGate, - DeallocateQubitGate, - H, FastForwardingGate, - ClassicalInstructionGate) +from projectq.ops import ( + AllocateQubitGate, + DeallocateQubitGate, + H, + FastForwardingGate, + ClassicalInstructionGate, +) from projectq.cengines import _basics @@ -64,13 +68,13 @@ def test_basic_engine_allocate_and_deallocate_qubit_and_qureg(): # any allocate or deallocate gates cmd_sent_by_main_engine = [] - def receive(self, cmd_list): cmd_sent_by_main_engine.append(cmd_list) + def receive(self, cmd_list): + cmd_sent_by_main_engine.append(cmd_list) eng.receive = types.MethodType(receive, eng) # Create test engines: saving_backend = DummyEngine(save_commands=True) - main_engine = MainEngine(backend=saving_backend, - engine_list=[eng, DummyEngine()]) + main_engine = MainEngine(backend=saving_backend, engine_list=[eng, DummyEngine()]) # Allocate and deallocate qubits qubit = eng.allocate_qubit() # Try to allocate dirty qubit but it should give a non dirty qubit @@ -80,8 +84,7 @@ def receive(self, cmd_list): cmd_sent_by_main_engine.append(cmd_list) def allow_dirty_qubits(self, meta_tag): return meta_tag == DirtyQubitTag - saving_backend.is_meta_tag_handler = types.MethodType(allow_dirty_qubits, - saving_backend) + saving_backend.is_meta_tag_handler = types.MethodType(allow_dirty_qubits, saving_backend) dirty_qubit = eng.allocate_qubit(dirty=True) qureg = eng.allocate_qureg(2) # Test qubit allocation @@ -107,8 +110,20 @@ def allow_dirty_qubits(self, meta_tag): assert tmp_qubit in main_engine.active_qubits assert id(tmp_qubit.engine) == id(eng) # Test uniqueness of ids - assert len(set([qubit[0].id, not_dirty_qubit[0].id, dirty_qubit[0].id, - qureg[0].id, qureg[1].id])) == 5 + assert ( + len( + set( + [ + qubit[0].id, + not_dirty_qubit[0].id, + dirty_qubit[0].id, + qureg[0].id, + qureg[1].id, + ] + ) + ) + == 5 + ) # Test allocate gates were sent assert len(cmd_sent_by_main_engine) == 0 assert len(saving_backend.received_commands) == 5 @@ -137,9 +152,11 @@ def test_deallocate_qubit_exception(): def test_basic_engine_is_meta_tag_supported(): eng = _basics.BasicEngine() + # BasicEngine needs receive function to function so let's add it: - def receive(self, cmd_list): self.send(cmd_list) + def receive(self, cmd_list): + self.send(cmd_list) eng.receive = types.MethodType(receive, eng) backend = DummyEngine() @@ -152,10 +169,8 @@ def allow_dirty_qubits(self, meta_tag): return True return False - engine2.is_meta_tag_handler = types.MethodType(allow_dirty_qubits, - engine2) - main_engine = MainEngine(backend=backend, - engine_list=[engine0, engine1, engine2]) + engine2.is_meta_tag_handler = types.MethodType(allow_dirty_qubits, engine2) + main_engine = MainEngine(backend=backend, engine_list=[engine0, engine1, engine2]) assert not main_engine.is_meta_tag_supported("NotSupported") assert main_engine.is_meta_tag_supported(DirtyQubitTag) @@ -163,8 +178,7 @@ def allow_dirty_qubits(self, meta_tag): def test_forwarder_engine(): backend = DummyEngine(save_commands=True) engine0 = DummyEngine() - main_engine = MainEngine(backend=backend, - engine_list=[engine0]) + main_engine = MainEngine(backend=backend, engine_list=[engine0]) def cmd_mod_fun(cmd): cmd.tags = "NewTag" @@ -180,8 +194,7 @@ def cmd_mod_fun(cmd): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) for cmd in received_commands: print(cmd) diff --git a/projectq/cengines/_cmdmodifier.py b/projectq/cengines/_cmdmodifier.py index 0a1df34c6..faf2440c6 100755 --- a/projectq/cengines/_cmdmodifier.py +++ b/projectq/cengines/_cmdmodifier.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Contains a CommandModifier engine, which can be used to, e.g., modify the tags of all commands which pass by (see the AutoReplacer for an example). @@ -25,6 +25,7 @@ class CommandModifier(BasicEngine): incoming commands, sending on the resulting command instead of the original one. """ + def __init__(self, cmd_mod_fun): """ Initialize the CommandModifier. diff --git a/projectq/cengines/_cmdmodifier_test.py b/projectq/cengines/_cmdmodifier_test.py index afc7e16a2..79f8d858e 100755 --- a/projectq/cengines/_cmdmodifier_test.py +++ b/projectq/cengines/_cmdmodifier_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.cengines._cmdmodifier.py.""" from projectq import MainEngine @@ -35,8 +35,7 @@ def cmd_mod_fun(cmd): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) for cmd in received_commands: print(cmd) diff --git a/projectq/cengines/_ibm5qubitmapper.py b/projectq/cengines/_ibm5qubitmapper.py index 2c85749d2..3b75c329b 100755 --- a/projectq/cengines/_ibm5qubitmapper.py +++ b/projectq/cengines/_ibm5qubitmapper.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,6 +37,7 @@ 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,10 +51,22 @@ def __init__(self, connections=None): 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)]) + # 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 @@ -109,30 +123,26 @@ 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): - raise RuntimeError("Too many qubits allocated. The IBM Q " - "device supports at most 5 qubits and no " - "intermediate measurements / " - "reallocations.") + 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 = 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)) - } + 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))} new_cost = self._determine_cost(mapping) if new_cost is not None: if best_cost is None or new_cost < best_cost: best_cost = new_cost best_mapping = mapping if best_cost is None: - raise RuntimeError("Circuit cannot be mapped without using " - "Swaps. Mapping failed.") + raise RuntimeError("Circuit cannot be mapped without using Swaps. Mapping failed.") self._interactions = dict() self.current_mapping = best_mapping @@ -194,5 +204,4 @@ def _is_cnot(cmd): cmd (Command): Command to check whether it is a controlled NOT gate. """ - return (isinstance(cmd.gate, NOT.__class__) - and get_control_count(cmd) == 1) + 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 ea6d383b6..29ed59092 100755 --- a/projectq/cengines/_ibm5qubitmapper_test.py +++ b/projectq/cengines/_ibm5qubitmapper_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,9 +39,8 @@ def test_ibm5qubitmapper_invalid_circuit(): backend = DummyEngine(save_commands=True) eng = MainEngine( backend=backend, - engine_list=[ - _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) - ]) + engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)], + ) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -59,9 +59,8 @@ def test_ibm5qubitmapper_valid_circuit1(): backend = DummyEngine(save_commands=True) eng = MainEngine( backend=backend, - engine_list=[ - _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) - ]) + engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)], + ) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -82,9 +81,8 @@ def test_ibm5qubitmapper_valid_circuit2(): backend = DummyEngine(save_commands=True) eng = MainEngine( backend=backend, - engine_list=[ - _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) - ]) + engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)], + ) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -114,9 +112,8 @@ class FakeIBMBackend(IBMBackend): eng = MainEngine( backend=fake, - engine_list=[ - _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity) - ]) + engine_list=[_ibm5qubitmapper.IBM5QubitMapper(connections=connectivity)], + ) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() @@ -139,12 +136,13 @@ def test_ibm5qubitmapper_optimizeifpossible(): backend=backend, engine_list=[ _ibm5qubitmapper.IBM5QubitMapper(connections=connectivity), - SwapAndCNOTFlipper(connectivity) - ]) - qb0 = eng.allocate_qubit() + SwapAndCNOTFlipper(connectivity), + ], + ) + qb0 = eng.allocate_qubit() # noqa: F841 qb1 = eng.allocate_qubit() qb2 = eng.allocate_qubit() - qb3 = eng.allocate_qubit() + qb3 = eng.allocate_qubit() # noqa: F841 CNOT | (qb1, qb2) CNOT | (qb2, qb1) CNOT | (qb1, qb2) @@ -176,11 +174,13 @@ def test_ibm5qubitmapper_optimizeifpossible(): 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) - ]) + eng = MainEngine( + backend=backend, + engine_list=[ + _ibm5qubitmapper.IBM5QubitMapper(), + SwapAndCNOTFlipper(connectivity), + ], + ) qubits = eng.allocate_qureg(6) All(H) | qubits CNOT | (qubits[0], qubits[1]) diff --git a/projectq/cengines/_linearmapper.py b/projectq/cengines/_linearmapper.py index 85ab72b3b..cd2d1149d 100644 --- a/projectq/cengines/_linearmapper.py +++ b/projectq/cengines/_linearmapper.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Mapper for a quantum circuit to a linear chain of qubits. @@ -23,14 +23,19 @@ Swap gates in order to move qubits next to each other. """ -from collections import deque from copy import deepcopy from projectq.cengines import BasicMapperEngine from projectq.meta import LogicalQubitIDTag -from projectq.ops import (Allocate, AllocateQubitGate, Deallocate, - DeallocateQubitGate, Command, FlushGate, - MeasureGate, Swap) +from projectq.ops import ( + Allocate, + AllocateQubitGate, + Deallocate, + DeallocateQubitGate, + Command, + FlushGate, + Swap, +) from projectq.types import WeakQubitRef @@ -122,8 +127,7 @@ def is_available(self, cmd): return False @staticmethod - def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, - stored_commands, current_mapping): + def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_commands, current_mapping): """ Builds a mapping of qubits to a linear chain. @@ -168,8 +172,7 @@ def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, neighbour_ids[qubit_id] = set() for cmd in stored_commands: - if (len(allocated_qubits) == num_qubits and - len(active_qubits) == 0): + if len(allocated_qubits) == num_qubits and len(active_qubits) == 0: break qubit_ids = [] @@ -178,8 +181,7 @@ def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, qubit_ids.append(qubit.id) if len(qubit_ids) > 2 or len(qubit_ids) == 0: - raise Exception("Invalid command (number of qubits): " + - str(cmd)) + raise Exception("Invalid command (number of qubits): " + str(cmd)) elif isinstance(cmd.gate, AllocateQubitGate): qubit_id = cmd.qubits[0][0].id @@ -208,17 +210,18 @@ def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, qubit1=qubit_ids[1], active_qubits=active_qubits, segments=segments, - neighbour_ids=neighbour_ids) + neighbour_ids=neighbour_ids, + ) return LinearMapper._return_new_mapping_from_segments( num_qubits=num_qubits, segments=segments, allocated_qubits=allocated_qubits, - current_mapping=current_mapping) + current_mapping=current_mapping, + ) @staticmethod - def _process_two_qubit_gate(num_qubits, cyclic, qubit0, qubit1, - active_qubits, segments, neighbour_ids): + def _process_two_qubit_gate(num_qubits, cyclic, qubit0, qubit1, active_qubits, segments, neighbour_ids): """ Processes a two qubit gate. @@ -304,21 +307,17 @@ def _process_two_qubit_gate(num_qubits, cyclic, qubit0, qubit1, # both qubits are at the end of different segments -> combine them else: if not qb0_is_left_end and qb1_is_left_end: - segments[segment_index_qb0].extend( - segments[segment_index_qb1]) + segments[segment_index_qb0].extend(segments[segment_index_qb1]) segments.pop(segment_index_qb1) elif not qb0_is_left_end and not qb1_is_left_end: - segments[segment_index_qb0].extend( - reversed(segments[segment_index_qb1])) + segments[segment_index_qb0].extend(reversed(segments[segment_index_qb1])) segments.pop(segment_index_qb1) elif qb0_is_left_end and qb1_is_left_end: segments[segment_index_qb0].reverse() - segments[segment_index_qb0].extend( - segments[segment_index_qb1]) + segments[segment_index_qb0].extend(segments[segment_index_qb1]) segments.pop(segment_index_qb1) else: - segments[segment_index_qb1].extend( - segments[segment_index_qb0]) + segments[segment_index_qb1].extend(segments[segment_index_qb0]) segments.pop(segment_index_qb0) # Add new neighbour ids and make sure to check cyclic neighbour_ids[qubit0].add(qubit1) @@ -329,8 +328,7 @@ def _process_two_qubit_gate(num_qubits, cyclic, qubit0, qubit1, return @staticmethod - def _return_new_mapping_from_segments(num_qubits, segments, - allocated_qubits, current_mapping): + def _return_new_mapping_from_segments(num_qubits, segments, allocated_qubits, current_mapping): """ Combines the individual segments into a new mapping. @@ -390,24 +388,28 @@ def _return_new_mapping_from_segments(num_qubits, segments, segment_ids = set(segment) segment_ids.discard(None) - overlap = len(previous_chain_ids.intersection( - segment_ids)) + previous_chain[idx0:idx1].count(None) + overlap = len(previous_chain_ids.intersection(segment_ids)) + previous_chain[idx0:idx1].count(None) if overlap == 0: overlap_fraction = 0 elif overlap == len(segment): overlap_fraction = 1 else: overlap_fraction = overlap / float(len(segment)) - if ((overlap_fraction == 1 and padding < best_padding) or - overlap_fraction > highest_overlap_fraction or - highest_overlap_fraction == 0): + if ( + (overlap_fraction == 1 and padding < best_padding) + or overlap_fraction > highest_overlap_fraction + or highest_overlap_fraction == 0 + ): best_segment = segment best_padding = padding highest_overlap_fraction = overlap_fraction # Add best segment and padding to new_chain - new_chain[current_position_to_fill+best_padding: - current_position_to_fill+best_padding + - len(best_segment)] = best_segment + new_chain[ + current_position_to_fill + + best_padding : current_position_to_fill # noqa: E203 + + best_padding + + len(best_segment) + ] = best_segment remaining_segments.remove(best_segment) current_position_to_fill += best_padding + len(best_segment) num_unused_qubits -= best_padding @@ -437,8 +439,7 @@ def _odd_even_transposition_sort_swaps(self, old_mapping, new_mapping): # move qubits which are in both mappings for logical_id in old_mapping: if logical_id in new_mapping: - final_positions[old_mapping[logical_id]] = new_mapping[ - logical_id] + final_positions[old_mapping[logical_id]] = new_mapping[logical_id] # exchange all remaining None with the not yet used mapped ids used_mapped_ids = set(final_positions) used_mapped_ids.discard(None) @@ -454,19 +455,19 @@ def _odd_even_transposition_sort_swaps(self, old_mapping, new_mapping): finished_sorting = False while not finished_sorting: finished_sorting = True - for i in range(1, len(final_positions)-1, 2): - if final_positions[i] > final_positions[i+1]: - swap_operations.append((i, i+1)) + for i in range(1, len(final_positions) - 1, 2): + if final_positions[i] > final_positions[i + 1]: + swap_operations.append((i, i + 1)) tmp = final_positions[i] - final_positions[i] = final_positions[i+1] - final_positions[i+1] = tmp + final_positions[i] = final_positions[i + 1] + final_positions[i + 1] = tmp finished_sorting = False - for i in range(0, len(final_positions)-1, 2): - if final_positions[i] > final_positions[i+1]: - swap_operations.append((i, i+1)) + for i in range(0, len(final_positions) - 1, 2): + if final_positions[i] > final_positions[i + 1]: + swap_operations.append((i, i + 1)) tmp = final_positions[i] - final_positions[i] = final_positions[i+1] - final_positions[i+1] = tmp + final_positions[i] = final_positions[i + 1] + final_positions[i + 1] = tmp finished_sorting = False return swap_operations @@ -489,27 +490,25 @@ def _send_possible_commands(self): if isinstance(cmd.gate, AllocateQubitGate): if cmd.qubits[0][0].id in self.current_mapping: self._currently_allocated_ids.add(cmd.qubits[0][0].id) - qb = WeakQubitRef( - engine=self, - idx=self.current_mapping[cmd.qubits[0][0].id]) + qb = WeakQubitRef(engine=self, idx=self.current_mapping[cmd.qubits[0][0].id]) new_cmd = Command( engine=self, gate=AllocateQubitGate(), qubits=([qb],), - tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)]) + tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)], + ) self.send([new_cmd]) else: new_stored_commands.append(cmd) elif isinstance(cmd.gate, DeallocateQubitGate): if cmd.qubits[0][0].id in active_ids: - qb = WeakQubitRef( - engine=self, - idx=self.current_mapping[cmd.qubits[0][0].id]) + qb = WeakQubitRef(engine=self, idx=self.current_mapping[cmd.qubits[0][0].id]) new_cmd = Command( engine=self, gate=DeallocateQubitGate(), qubits=([qb],), - tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)]) + tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)], + ) self._currently_allocated_ids.remove(cmd.qubits[0][0].id) active_ids.remove(cmd.qubits[0][0].id) self._current_mapping.pop(cmd.qubits[0][0].id) @@ -528,9 +527,9 @@ def _send_possible_commands(self): # Check that mapped ids are nearest neighbour if len(mapped_ids) == 2: mapped_ids = list(mapped_ids) - diff = abs(mapped_ids[0]-mapped_ids[1]) + diff = abs(mapped_ids[0] - mapped_ids[1]) if self.cyclic: - if diff != 1 and diff != self.num_qubits-1: + if diff != 1 and diff != self.num_qubits - 1: send_gate = False else: if diff != 1: @@ -561,21 +560,21 @@ def _run(self): self._send_possible_commands() if len(self._stored_commands) == 0: return - new_mapping = self.return_new_mapping(self.num_qubits, - self.cyclic, - self._currently_allocated_ids, - self._stored_commands, - self.current_mapping) - swaps = self._odd_even_transposition_sort_swaps( - old_mapping=self.current_mapping, new_mapping=new_mapping) + new_mapping = self.return_new_mapping( + self.num_qubits, + self.cyclic, + self._currently_allocated_ids, + self._stored_commands, + self.current_mapping, + ) + swaps = self._odd_even_transposition_sort_swaps(old_mapping=self.current_mapping, new_mapping=new_mapping) if swaps: # first mapping requires no swaps # Allocate all mapped qubit ids (which are not already allocated, # i.e., contained in self._currently_allocated_ids) mapped_ids_used = set() for logical_id in self._currently_allocated_ids: mapped_ids_used.add(self.current_mapping[logical_id]) - not_allocated_ids = set(range(self.num_qubits)).difference( - mapped_ids_used) + not_allocated_ids = set(range(self.num_qubits)).difference(mapped_ids_used) for mapped_id in not_allocated_ids: qb = WeakQubitRef(engine=self, idx=mapped_id) cmd = Command(engine=self, gate=Allocate, qubits=([qb],)) @@ -602,12 +601,10 @@ def _run(self): mapped_ids_used = set() for logical_id in self._currently_allocated_ids: mapped_ids_used.add(new_mapping[logical_id]) - not_needed_anymore = set(range(self.num_qubits)).difference( - mapped_ids_used) + not_needed_anymore = set(range(self.num_qubits)).difference(mapped_ids_used) for mapped_id in not_needed_anymore: qb = WeakQubitRef(engine=self, idx=mapped_id) - cmd = Command(engine=self, gate=Deallocate, - qubits=([qb],)) + cmd = Command(engine=self, gate=Deallocate, qubits=([qb],)) self.send([cmd]) # Change to new map: self.current_mapping = new_mapping @@ -615,10 +612,12 @@ def _run(self): self._send_possible_commands() # Check that mapper actually made progress if len(self._stored_commands) == num_of_stored_commands_before: - raise RuntimeError("Mapper is potentially in an infinite loop. " - "It is likely that the algorithm requires " - "too many qubits. Increase the number of " - "qubits for this mapper.") + raise RuntimeError( + "Mapper is potentially in an infinite loop. " + "It is likely that the algorithm requires " + "too many qubits. Increase the number of " + "qubits for this mapper." + ) def receive(self, command_list): """ @@ -631,7 +630,7 @@ def receive(self, command_list): """ for cmd in command_list: if isinstance(cmd.gate, FlushGate): - while(len(self._stored_commands)): + while len(self._stored_commands): self._run() self.send([cmd]) else: diff --git a/projectq/cengines/_linearmapper_test.py b/projectq/cengines/_linearmapper_test.py index dd76da617..efe6b68d2 100644 --- a/projectq/cengines/_linearmapper_test.py +++ b/projectq/cengines/_linearmapper_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.cengines._linearmapper.py.""" from copy import deepcopy @@ -19,8 +19,16 @@ from projectq.cengines import DummyEngine from projectq.meta import LogicalQubitIDTag -from projectq.ops import (Allocate, BasicGate, CNOT, Command, Deallocate, - FlushGate, Measure, QFT, X) +from projectq.ops import ( + Allocate, + BasicGate, + CNOT, + Command, + Deallocate, + FlushGate, + QFT, + X, +) from projectq.types import WeakQubitRef from projectq.cengines import _linearmapper as lm @@ -63,7 +71,8 @@ def test_return_new_mapping_too_many_qubits(): cyclic=mapper.cyclic, currently_allocated_ids=mapper._currently_allocated_ids, stored_commands=mapper._stored_commands, - current_mapping=mapper.current_mapping) + current_mapping=mapper.current_mapping, + ) cmd1 = Command(None, BasicGate(), qubits=([],)) mapper._stored_commands = [cmd1] with pytest.raises(Exception): @@ -72,7 +81,8 @@ def test_return_new_mapping_too_many_qubits(): cyclic=mapper.cyclic, currently_allocated_ids=mapper._currently_allocated_ids, stored_commands=mapper._stored_commands, - current_mapping=mapper.current_mapping) + current_mapping=mapper.current_mapping, + ) def test_return_new_mapping_allocate_qubits(): @@ -88,7 +98,8 @@ def test_return_new_mapping_allocate_qubits(): cyclic=mapper.cyclic, currently_allocated_ids=mapper._currently_allocated_ids, stored_commands=mapper._stored_commands, - current_mapping=mapper.current_mapping) + current_mapping=mapper.current_mapping, + ) assert mapper._currently_allocated_ids == set([4]) assert mapper._stored_commands == [cmd0, cmd1] assert len(new_mapping) == 2 @@ -98,7 +109,7 @@ def test_return_new_mapping_allocate_qubits(): def test_return_new_mapping_allocate_only_once(): mapper = lm.LinearMapper(num_qubits=1, cyclic=False) qb0 = WeakQubitRef(engine=None, idx=0) - qb1 = WeakQubitRef(engine=None, idx=1) + qb1 = WeakQubitRef(engine=None, idx=1) # noqa: F841 mapper._currently_allocated_ids = set() cmd0 = Command(None, Allocate, ([qb0],)) cmd1 = Command(None, Deallocate, ([qb0],)) @@ -106,12 +117,13 @@ def test_return_new_mapping_allocate_only_once(): # This would otherwise trigger an error (test by num_qubits=2) cmd2 = None mapper._stored_commands = [cmd0, cmd1, cmd2] - new_mapping = mapper.return_new_mapping( + mapper.return_new_mapping( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, currently_allocated_ids=mapper._currently_allocated_ids, stored_commands=mapper._stored_commands, - current_mapping=mapper.current_mapping) + current_mapping=mapper.current_mapping, + ) def test_return_new_mapping_possible_map(): @@ -131,9 +143,9 @@ def test_return_new_mapping_possible_map(): cyclic=mapper.cyclic, currently_allocated_ids=mapper._currently_allocated_ids, stored_commands=mapper._stored_commands, - current_mapping=mapper.current_mapping) - assert (new_mapping == {0: 2, 1: 1, 2: 0} or - new_mapping == {0: 0, 1: 1, 2: 2}) + current_mapping=mapper.current_mapping, + ) + assert new_mapping == {0: 2, 1: 1, 2: 0} or new_mapping == {0: 0, 1: 1, 2: 2} def test_return_new_mapping_previous_error(): @@ -148,12 +160,13 @@ def test_return_new_mapping_previous_error(): cmd3 = Command(None, Allocate, ([qb3],)) cmd4 = Command(None, CNOT, qubits=([qb2],), controls=[qb3]) mapper._stored_commands = [cmd0, cmd1, cmd2, cmd3, cmd4] - new_mapping = mapper.return_new_mapping( + mapper.return_new_mapping( num_qubits=mapper.num_qubits, cyclic=mapper.cyclic, currently_allocated_ids=mapper._currently_allocated_ids, stored_commands=mapper._stored_commands, - current_mapping=mapper.current_mapping) + current_mapping=mapper.current_mapping, + ) def test_process_two_qubit_gate_not_in_segments_test0(): @@ -161,13 +174,15 @@ def test_process_two_qubit_gate_not_in_segments_test0(): segments = [[0, 1]] active_qubits = set([0, 1, 4, 6]) neighbour_ids = {0: set([1]), 1: set([0]), 4: set(), 6: set()} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=4, - qubit1=6, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=4, + qubit1=6, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert len(segments) == 2 assert segments[0] == [0, 1] assert segments[1] == [4, 6] @@ -181,13 +196,15 @@ def test_process_two_qubit_gate_not_in_segments_test1(): segments = [] active_qubits = set([4, 6]) neighbour_ids = {4: set(), 6: set()} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=5, - qubit1=6, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=5, + qubit1=6, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert len(segments) == 0 assert active_qubits == set([4]) @@ -199,13 +216,15 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment(qb0, qb1): segments = [[0, 1]] active_qubits = set([0, 1, 2]) neighbour_ids = {0: set([1]), 1: set([0]), 2: set()} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=qb0, - qubit1=qb1, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=qb0, + qubit1=qb1, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert segments == [[0, 1, 2]] assert active_qubits == set([0, 1, 2]) assert neighbour_ids[1] == set([0, 2]) @@ -219,13 +238,15 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment2(qb0, qb1): segments = [[1, 2]] active_qubits = set([0, 1, 2]) neighbour_ids = {0: set([]), 1: set([2]), 2: set([1])} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=qb0, - qubit1=qb1, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=qb0, + qubit1=qb1, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert segments == [[0, 1, 2]] assert active_qubits == set([0, 1, 2]) assert neighbour_ids[1] == set([0, 2]) @@ -238,13 +259,15 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_segment_cycle(qb0, qb1): segments = [[0, 1]] active_qubits = set([0, 1, 2]) neighbour_ids = {0: set([1]), 1: set([0]), 2: set()} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=qb0, - qubit1=qb1, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=qb0, + qubit1=qb1, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert segments == [[0, 1, 2]] assert active_qubits == set([0, 1, 2]) assert neighbour_ids[1] == set([0, 2]) @@ -258,13 +281,15 @@ def test_process_two_qubit_gate_one_qb_free_one_qb_in_seg_cycle2(qb0, qb1): segments = [[0, 1]] active_qubits = set([0, 1, 2]) neighbour_ids = {0: set([1]), 1: set([0]), 2: set()} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=qb0, - qubit1=qb1, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=qb0, + qubit1=qb1, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert segments == [[0, 1, 2]] assert active_qubits == set([0, 1, 2]) assert neighbour_ids[1] == set([0, 2]) @@ -276,13 +301,15 @@ def test_process_two_qubit_gate_one_qubit_in_middle_of_segment(): segments = [] active_qubits = set([0, 1, 2, 3]) neighbour_ids = {0: set([1]), 1: set([0, 2]), 2: set([1]), 3: set()} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=1, - qubit1=3, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=1, + qubit1=3, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert len(segments) == 0 assert active_qubits == set([0, 2]) @@ -292,13 +319,15 @@ def test_process_two_qubit_gate_both_in_same_segment(): segments = [[0, 1, 2]] active_qubits = set([0, 1, 2]) neighbour_ids = {0: set([1]), 1: set([0, 2]), 2: set([1])} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=0, - qubit1=2, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=0, + qubit1=2, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert segments == [[0, 1, 2]] assert active_qubits == set([1]) @@ -308,52 +337,70 @@ def test_process_two_qubit_gate_already_connected(): segments = [[0, 1, 2]] active_qubits = set([0, 1, 2]) neighbour_ids = {0: set([1]), 1: set([0, 2]), 2: set([1])} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=0, - qubit1=1, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=0, + qubit1=1, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert segments == [[0, 1, 2]] assert active_qubits == set([0, 1, 2]) -@pytest.mark.parametrize("qb0, qb1, result_seg", [ - (0, 2, [1, 0, 2, 3]), (0, 3, [2, 3, 0, 1]), (1, 2, [0, 1, 2, 3]), - (1, 3, [0, 1, 3, 2])]) +@pytest.mark.parametrize( + "qb0, qb1, result_seg", + [ + (0, 2, [1, 0, 2, 3]), + (0, 3, [2, 3, 0, 1]), + (1, 2, [0, 1, 2, 3]), + (1, 3, [0, 1, 3, 2]), + ], +) def test_process_two_qubit_gate_combine_segments(qb0, qb1, result_seg): mapper = lm.LinearMapper(num_qubits=4, cyclic=False) segments = [[0, 1], [2, 3]] active_qubits = set([0, 1, 2, 3, 4]) neighbour_ids = {0: set([1]), 1: set([0]), 2: set([3]), 3: set([2])} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=qb0, - qubit1=qb1, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=qb0, + qubit1=qb1, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert segments == [result_seg] or segments == [reversed(result_seg)] assert qb1 in neighbour_ids[qb0] assert qb0 in neighbour_ids[qb1] -@pytest.mark.parametrize("qb0, qb1, result_seg", [ - (0, 2, [1, 0, 2, 3]), (0, 3, [2, 3, 0, 1]), (1, 2, [0, 1, 2, 3]), - (1, 3, [0, 1, 3, 2])]) +@pytest.mark.parametrize( + "qb0, qb1, result_seg", + [ + (0, 2, [1, 0, 2, 3]), + (0, 3, [2, 3, 0, 1]), + (1, 2, [0, 1, 2, 3]), + (1, 3, [0, 1, 3, 2]), + ], +) def test_process_two_qubit_gate_combine_segments_cycle(qb0, qb1, result_seg): mapper = lm.LinearMapper(num_qubits=4, cyclic=True) segments = [[0, 1], [2, 3]] active_qubits = set([0, 1, 2, 3, 4]) neighbour_ids = {0: set([1]), 1: set([0]), 2: set([3]), 3: set([2])} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=qb0, - qubit1=qb1, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=qb0, + qubit1=qb1, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert segments == [result_seg] or segments == [reversed(result_seg)] assert qb1 in neighbour_ids[qb0] assert qb0 in neighbour_ids[qb1] @@ -361,22 +408,30 @@ def test_process_two_qubit_gate_combine_segments_cycle(qb0, qb1, result_seg): assert result_seg[-1] in neighbour_ids[result_seg[0]] -@pytest.mark.parametrize("qb0, qb1, result_seg", [ - (0, 2, [1, 0, 2, 3]), (0, 3, [2, 3, 0, 1]), (1, 2, [0, 1, 2, 3]), - (1, 3, [0, 1, 3, 2])]) +@pytest.mark.parametrize( + "qb0, qb1, result_seg", + [ + (0, 2, [1, 0, 2, 3]), + (0, 3, [2, 3, 0, 1]), + (1, 2, [0, 1, 2, 3]), + (1, 3, [0, 1, 3, 2]), + ], +) def test_process_two_qubit_gate_combine_segments_cycle2(qb0, qb1, result_seg): # Not long enough segment for cyclic mapper = lm.LinearMapper(num_qubits=5, cyclic=True) segments = [[0, 1], [2, 3]] active_qubits = set([0, 1, 2, 3, 4]) neighbour_ids = {0: set([1]), 1: set([0]), 2: set([3]), 3: set([2])} - mapper._process_two_qubit_gate(num_qubits=mapper.num_qubits, - cyclic=mapper.cyclic, - qubit0=qb0, - qubit1=qb1, - active_qubits=active_qubits, - segments=segments, - neighbour_ids=neighbour_ids) + mapper._process_two_qubit_gate( + num_qubits=mapper.num_qubits, + cyclic=mapper.cyclic, + qubit0=qb0, + qubit1=qb1, + active_qubits=active_qubits, + segments=segments, + neighbour_ids=neighbour_ids, + ) assert segments == [result_seg] or segments == [reversed(result_seg)] assert qb1 in neighbour_ids[qb0] assert qb0 in neighbour_ids[qb1] @@ -385,13 +440,15 @@ def test_process_two_qubit_gate_combine_segments_cycle2(qb0, qb1, result_seg): @pytest.mark.parametrize( - "segments, current_chain, correct_chain, allocated_qubits", [ + "segments, current_chain, correct_chain, allocated_qubits", + [ ([[0, 2, 4]], [0, 1, 2, 3, 4], [0, 2, 4, 3, 1], [0, 1, 2, 3, 4]), ([[0, 2, 4]], [0, 1, 2, 3, 4], [0, 2, 4, 3, None], [0, 2, 3, 4]), ([[1, 2], [3, 0]], [0, 1, 2, 3, 4], [None, 1, 2, 3, 0], [0, 1, 2, 3]), - ([[1, 2], [3, 0]], [0, 1, 2, 3, 4], [1, 2, 3, 0, 4], [0, 1, 2, 3, 4])]) -def test_return_new_mapping_from_segments(segments, current_chain, - correct_chain, allocated_qubits): + ([[1, 2], [3, 0]], [0, 1, 2, 3, 4], [1, 2, 3, 0, 4], [0, 1, 2, 3, 4]), + ], +) +def test_return_new_mapping_from_segments(segments, current_chain, correct_chain, allocated_qubits): mapper = lm.LinearMapper(num_qubits=5, cyclic=False) current_mapping = dict() for pos, logical_id in enumerate(current_chain): @@ -401,7 +458,8 @@ def test_return_new_mapping_from_segments(segments, current_chain, num_qubits=mapper.num_qubits, segments=segments, allocated_qubits=allocated_qubits, - current_mapping=mapper.current_mapping) + current_mapping=mapper.current_mapping, + ) correct_mapping = dict() for pos, logical_id in enumerate(correct_chain): if logical_id is not None: @@ -409,12 +467,15 @@ def test_return_new_mapping_from_segments(segments, current_chain, assert correct_mapping == new_mapping -@pytest.mark.parametrize("old_chain, new_chain", [ - ([0, 1, 2, 3, 4], [4, 3, 2, 1, 0]), - ([2, 0, 14, 44, 12], [14, 12, 44, 0, 2]), - ([2, None, 14, 44, 12], [14, 1, 44, 0, 2]), - ([2, None, 14, 44, 12], [14, None, 44, 0, 2]) - ]) +@pytest.mark.parametrize( + "old_chain, new_chain", + [ + ([0, 1, 2, 3, 4], [4, 3, 2, 1, 0]), + ([2, 0, 14, 44, 12], [14, 12, 44, 0, 2]), + ([2, None, 14, 44, 12], [14, 1, 44, 0, 2]), + ([2, None, 14, 44, 12], [14, None, 44, 0, 2]), + ], +) def test_odd_even_transposition_sort_swaps(old_chain, new_chain): mapper = lm.LinearMapper(num_qubits=5, cyclic=False) old_map = dict() @@ -447,8 +508,7 @@ def test_send_possible_commands_allocate(): backend.is_last_engine = True mapper.next_engine = backend qb0 = WeakQubitRef(engine=None, idx=0) - cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], - tags=[]) + cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] mapper._currently_allocated_ids = set([10]) # not in mapping: @@ -474,8 +534,7 @@ def test_send_possible_commands_deallocate(): backend.is_last_engine = True mapper.next_engine = backend qb0 = WeakQubitRef(engine=None, idx=0) - cmd0 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], - tags=[]) + cmd0 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] mapper.current_mapping = dict() mapper._currently_allocated_ids = set([10]) @@ -503,12 +562,9 @@ def test_send_possible_commands_keep_remaining_gates(): mapper.next_engine = backend qb0 = WeakQubitRef(engine=None, idx=0) qb1 = WeakQubitRef(engine=None, idx=1) - cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], - tags=[]) - cmd1 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], - tags=[]) - cmd2 = Command(engine=None, gate=Allocate, qubits=([qb1],), controls=[], - tags=[]) + cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], tags=[]) + cmd1 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], tags=[]) + cmd2 = Command(engine=None, gate=Allocate, qubits=([qb1],), controls=[], tags=[]) mapper._stored_commands = [cmd0, cmd1, cmd2] mapper.current_mapping = {0: 0} @@ -535,9 +591,7 @@ def test_send_possible_commands_not_cyclic(): mapper.current_mapping = {0: 0, 2: 1, 3: 2, 1: 3} mapper._send_possible_commands() assert len(backend.received_commands) == 2 - assert (backend.received_commands[0] == Command(None, CNOT, - qubits=([qb0],), - controls=[qb1])) + assert backend.received_commands[0] == Command(None, CNOT, qubits=([qb0],), controls=[qb1]) assert backend.received_commands[1] == Command(None, X, qubits=([qb0],)) # Following chain 0 <-> 2 <-> 1 <-> 3 mapper.current_mapping = {0: 0, 2: 1, 3: 3, 1: 2} @@ -565,9 +619,7 @@ def test_send_possible_commands_cyclic(): mapper.current_mapping = {0: 0, 2: 1, 3: 2, 1: 3} mapper._send_possible_commands() assert len(backend.received_commands) == 2 - assert (backend.received_commands[0] == Command(None, CNOT, - qubits=([qb0],), - controls=[qb3])) + assert backend.received_commands[0] == Command(None, CNOT, qubits=([qb0],), controls=[qb3]) assert backend.received_commands[1] == Command(None, X, qubits=([qb0],)) # Following chain 0 <-> 2 <-> 1 <-> 3 mapper.current_mapping = {0: 0, 2: 1, 3: 3, 1: 2} @@ -598,8 +650,10 @@ def test_run_and_receive(): assert mapper._stored_commands == [] assert len(backend.received_commands) == 7 assert mapper._currently_allocated_ids == set([0, 2]) - assert (mapper.current_mapping == {0: 2, 2: 0} or - mapper.current_mapping == {0: 0, 2: 2}) + assert mapper.current_mapping == {0: 2, 2: 0} or mapper.current_mapping == { + 0: 0, + 2: 2, + } cmd6 = Command(None, X, qubits=([qb0],), controls=[qb2]) mapper.storage = 1 mapper.receive([cmd6]) @@ -611,11 +665,12 @@ def test_run_and_receive(): assert len(backend.received_commands) == 11 for cmd in backend.received_commands: print(cmd) - assert (backend.received_commands[-1] == Command(None, X, - qubits=([WeakQubitRef(engine=None, - idx=mapper.current_mapping[qb0.id])],), - controls=[WeakQubitRef(engine=None, - idx=mapper.current_mapping[qb2.id])])) + assert backend.received_commands[-1] == Command( + None, + X, + qubits=([WeakQubitRef(engine=None, idx=mapper.current_mapping[qb0.id])],), + controls=[WeakQubitRef(engine=None, idx=mapper.current_mapping[qb2.id])], + ) assert mapper.num_mappings == 1 @@ -679,7 +734,8 @@ def test_send_possible_cmds_before_new_mapping(): backend.is_last_engine = True mapper.next_engine = backend - def dont_call_mapping(): raise Exception + def dont_call_mapping(): + raise Exception mapper._return_new_mapping = dont_call_mapping mapper.current_mapping = {0: 1} @@ -710,6 +766,5 @@ def test_correct_stats(): cmd8 = Command(None, X, qubits=([qb1],), controls=[qb2]) qb_flush = WeakQubitRef(engine=None, idx=-1) cmd_flush = Command(engine=None, gate=FlushGate(), qubits=([qb_flush],)) - mapper.receive([cmd0, cmd1, cmd2, cmd3, cmd4, cmd5, cmd6, cmd7, cmd8, - cmd_flush]) + mapper.receive([cmd0, cmd1, cmd2, cmd3, cmd4, cmd5, cmd6, cmd7, cmd8, cmd_flush]) assert mapper.num_mappings == 2 diff --git a/projectq/cengines/_main.py b/projectq/cengines/_main.py index f9bc0dbfe..3eaea3ef8 100755 --- a/projectq/cengines/_main.py +++ b/projectq/cengines/_main.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Contains the main engine of every compiler engine pipeline, called MainEngine. """ @@ -21,7 +21,6 @@ import traceback import weakref -import projectq from projectq.cengines import BasicEngine, BasicMapperEngine from projectq.ops import Command, FlushGate from projectq.types import WeakQubitRef @@ -54,6 +53,7 @@ class MainEngine(BasicEngine): mapper (BasicMapperEngine): Access to the mapper if there is one. """ + def __init__(self, backend=None, engine_list=None, verbose=False): """ Initialize the main compiler engine and all compiler engines. @@ -114,10 +114,12 @@ def __init__(self, backend=None, engine_list=None, verbose=False): "i.e. not an instance of BasicEngine.\n" "Did you forget the brackets to create an instance?\n" "E.g. MainEngine(backend=Simulator) instead of \n" - " MainEngine(backend=Simulator())") + " MainEngine(backend=Simulator())" + ) # default engine_list is projectq.setups.default.get_engine_list() if engine_list is None: import projectq.setups.default + engine_list = projectq.setups.default.get_engine_list() self.mapper = None @@ -130,16 +132,15 @@ def __init__(self, backend=None, engine_list=None, verbose=False): "\ni.e. not an instance of BasicEngine.\n" "Did you forget the brackets to create an instance?\n" "E.g. MainEngine(engine_list=[AutoReplacer]) instead " - "of\n MainEngine(engine_list=[AutoReplacer()])") + "of\n MainEngine(engine_list=[AutoReplacer()])" + ) if isinstance(current_eng, BasicMapperEngine): if self.mapper is None: self.mapper = current_eng else: - raise UnsupportedEngineError( - "More than one mapper engine is not supported.") + raise UnsupportedEngineError("More than one mapper engine is not supported.") else: - raise UnsupportedEngineError( - "The provided list of engines is not a list!") + raise UnsupportedEngineError("The provided list of engines is not a list!") engine_list = engine_list + [backend] self.backend = backend @@ -150,7 +151,8 @@ def __init__(self, backend=None, engine_list=None, verbose=False): "\nError:\n You supplied twice the same engine as backend" " or item in engine_list. This doesn't work. Create two \n" " separate instances of a compiler engine if it is needed\n" - " twice.\n") + " twice.\n" + ) self._qubit_idx = int(0) for i in range(len(engine_list) - 1): @@ -193,7 +195,7 @@ def __del__(self): self.flush(deallocate_qubits=True) try: atexit.unregister(self._delfun) # only available in Python3 - except AttributeError: + except AttributeError: # pragma: no cover pass def set_measurement_result(self, qubit, value): @@ -243,7 +245,8 @@ def get_measurement_result(self, qubit): "2. You have not yet called engine.flush() to " "force execution of your code\n\t3. The " "underlying backend failed to register " - "the measurement result\n") + "the measurement result\n" + ) def get_new_qubit_id(self): """ @@ -253,7 +256,7 @@ def get_new_qubit_id(self): new_qubit_id (int): New unique qubit id. """ self._qubit_idx += 1 - return (self._qubit_idx - 1) + return self._qubit_idx - 1 def receive(self, command_list): """ @@ -273,17 +276,16 @@ def send(self, command_list): """ try: self.next_engine.receive(command_list) - except: + except Exception: if self.verbose: raise else: exc_type, exc_value, exc_traceback = sys.exc_info() # try: last_line = traceback.format_exc().splitlines() - compact_exception = exc_type(str(exc_value) + - '\n raised in:\n' + - repr(last_line[-3]) + - "\n" + repr(last_line[-2])) + compact_exception = exc_type( + str(exc_value) + '\n raised in:\n' + repr(last_line[-3]) + "\n" + repr(last_line[-2]) + ) compact_exception.__cause__ = None raise compact_exception # use verbose=True for more info diff --git a/projectq/cengines/_main_test.py b/projectq/cengines/_main_test.py index a79a1a57f..078ccad6e 100755 --- a/projectq/cengines/_main_test.py +++ b/projectq/cengines/_main_test.py @@ -1,4 +1,5 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# -*- coding: utf-8 -*- +# Copyright 2017, 2021 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. @@ -11,18 +12,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.cengines._main.py.""" import sys import weakref import pytest -import projectq.setups.default from projectq.cengines import DummyEngine, BasicMapperEngine, LocalOptimizer from projectq.backends import Simulator -from projectq.ops import (AllocateQubitGate, DeallocateQubitGate, FlushGate, - H, X) +from projectq.ops import AllocateQubitGate, DeallocateQubitGate, FlushGate, H from projectq.cengines import _main @@ -50,14 +48,14 @@ def test_main_engine_init(): def test_main_engine_init_failure(): with pytest.raises(_main.UnsupportedEngineError): - eng = _main.MainEngine(backend=DummyEngine) + _main.MainEngine(backend=DummyEngine) with pytest.raises(_main.UnsupportedEngineError): - eng = _main.MainEngine(engine_list=DummyEngine) + _main.MainEngine(engine_list=DummyEngine) with pytest.raises(_main.UnsupportedEngineError): - eng = _main.MainEngine(engine_list=[DummyEngine(), DummyEngine]) + _main.MainEngine(engine_list=[DummyEngine(), DummyEngine]) with pytest.raises(_main.UnsupportedEngineError): engine = DummyEngine() - eng = _main.MainEngine(backend=engine, engine_list=[engine]) + _main.MainEngine(backend=engine, engine_list=[engine]) def test_main_engine_init_defaults(): @@ -69,13 +67,13 @@ def test_main_engine_init_defaults(): current_engine = current_engine.next_engine assert isinstance(eng_list[-1].next_engine, Simulator) import projectq.setups.default + default_engines = projectq.setups.default.get_engine_list() for engine, expected in zip(eng_list, default_engines): assert type(engine) == type(expected) def test_main_engine_init_mapper(): - class LinearMapper(BasicMapperEngine): pass @@ -89,7 +87,7 @@ class LinearMapper(BasicMapperEngine): assert eng2.mapper == mapper2 engine_list3 = [mapper1, mapper2] with pytest.raises(_main.UnsupportedEngineError): - eng3 = _main.MainEngine(engine_list=engine_list3) + _main.MainEngine(engine_list=engine_list3) def test_main_engine_del(): @@ -152,7 +150,7 @@ def test_main_engine_atexit_no_error(): del sys.last_type backend = DummyEngine(save_commands=True) eng = _main.MainEngine(backend=backend, engine_list=[]) - qb = eng.allocate_qubit() + qb = eng.allocate_qubit() # noqa: F841 eng._delfun(weakref.ref(eng)) assert len(backend.received_commands) == 3 assert backend.received_commands[0].gate == AllocateQubitGate() @@ -164,7 +162,7 @@ def test_main_engine_atexit_with_error(): sys.last_type = "Something" backend = DummyEngine(save_commands=True) eng = _main.MainEngine(backend=backend, engine_list=[]) - qb = eng.allocate_qubit() + qb = eng.allocate_qubit() # noqa: F841 eng._delfun(weakref.ref(eng)) assert len(backend.received_commands) == 1 assert backend.received_commands[0].gate == AllocateQubitGate() @@ -174,10 +172,16 @@ def test_exceptions_are_forwarded(): class ErrorEngine(DummyEngine): def receive(self, command_list): raise TypeError + eng = _main.MainEngine(backend=ErrorEngine(), engine_list=[]) with pytest.raises(TypeError): - eng.allocate_qubit() - eng2 = _main.MainEngine(backend=ErrorEngine(), engine_list=[], - verbose=True) + qb = eng.allocate_qubit() # noqa: F841 + eng2 = _main.MainEngine(backend=ErrorEngine(), engine_list=[]) with pytest.raises(TypeError): - eng2.allocate_qubit() + qb = eng2.allocate_qubit() # noqa: F841 + + # NB: avoid throwing exceptions when destroying the MainEngine + eng.next_engine = DummyEngine() + eng.next_engine.is_last_engine = True + eng2.next_engine = DummyEngine() + eng2.next_engine.is_last_engine = True diff --git a/projectq/cengines/_manualmapper.py b/projectq/cengines/_manualmapper.py index 323cfe5ed..75364efdb 100755 --- a/projectq/cengines/_manualmapper.py +++ b/projectq/cengines/_manualmapper.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,12 +12,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. - """ Contains a compiler engine to add mapping information """ from projectq.cengines import BasicMapperEngine -from projectq.ops import Measure class ManualMapper(BasicMapperEngine): diff --git a/projectq/cengines/_manualmapper_test.py b/projectq/cengines/_manualmapper_test.py index efff9a560..64a04dfd6 100755 --- a/projectq/cengines/_manualmapper_test.py +++ b/projectq/cengines/_manualmapper_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,18 +12,14 @@ # 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._manualmapper.py.""" -import pytest - from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import H, Allocate, Measure, All +from projectq.ops import H, Measure, All from projectq.meta import LogicalQubitIDTag from projectq.cengines import ManualMapper -from projectq.backends import IBMBackend def test_manualmapper_mapping(): @@ -31,8 +28,7 @@ def test_manualmapper_mapping(): def mapping(qubit_id): return (qubit_id + 1) & 1 - eng = MainEngine(backend=backend, - engine_list=[ManualMapper(mapping)]) + eng = MainEngine(backend=backend, engine_list=[ManualMapper(mapping)]) qb0 = eng.allocate_qubit() qb1 = eng.allocate_qubit() H | qb0 diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index 0c9765288..cd9c9b7b0 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,13 +12,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. - """ Contains a local optimizer engine. """ -from copy import deepcopy as _deepcopy -from projectq.cengines import LastEngineException, BasicEngine +from projectq.cengines import BasicEngine from projectq.ops import FlushGate, FastForwardingGate, NotMergeable @@ -33,6 +32,7 @@ class LocalOptimizer(BasicEngine): available). For examples, see BasicRotationGate. Once a list corresponding to a qubit contains >=m gates, the pipeline is sent on to the next engine. """ + def __init__(self, m=5): """ Initialize a LocalOptimizer object. @@ -54,10 +54,7 @@ def _send_qubit_pipeline(self, idx, n): for i in range(min(n, len(il))): # loop over first n operations # send all gates before n-qubit gate for other qubits involved # --> recursively call send_helper - other_involved_qubits = [qb - for qreg in il[i].all_qubits - for qb in qreg - if qb.id != idx] + other_involved_qubits = [qb for qreg in il[i].all_qubits for qb in qreg if qb.id != idx] for qb in other_involved_qubits: Id = qb.id try: @@ -73,9 +70,8 @@ def _send_qubit_pipeline(self, idx, n): # delete the n-qubit gate, we're taking care of it # and don't want the other qubit to do so self._l[Id] = self._l[Id][1:] - except IndexError: - print("Invalid qubit pipeline encountered (in the" - " process of shutting down?).") + except IndexError: # pragma: no cover + print("Invalid qubit pipeline encountered (in the process of shutting down?).") # all qubits that need to be flushed have been flushed # --> send on the n-qubit gate @@ -103,42 +99,37 @@ def _get_gate_indices(self, idx, i, IDs): # count how many there are, and skip over them when looking in the # other lists. cmd = self._l[idx][i] - num_identical_to_skip = sum(1 - for prev_cmd in self._l[idx][:i] - if prev_cmd == cmd) + num_identical_to_skip = sum(1 for prev_cmd in self._l[idx][:i] if prev_cmd == cmd) indices = [] for Id in IDs: - identical_indices = [i - for i, c in enumerate(self._l[Id]) - if c == cmd] + identical_indices = [i for i, c in enumerate(self._l[Id]) if c == cmd] indices.append(identical_indices[num_identical_to_skip]) return indices 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 - get_inverse functions of the gate (see, e.g., BasicRotationGate). + 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. """ # loop over all qubit indices i = 0 - new_gateloc = 0 limit = len(self._l[idx]) if lim is not None: limit = lim - 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] + 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:]) + new_list = ( + self._l[qubitids[j]][0 : gid[j]] # noqa: E203 + + self._l[qubitids[j]][gid[j] + 1 :] # noqa: E203 + ) self._l[qubitids[j]] = new_list i = 0 limit -= 1 @@ -149,20 +140,21 @@ def _optimize(self, idx, lim=None): if inv == self._l[idx][i + 1]: # determine index of this gate on all qubits - qubitids = [qb.id for sublist in self._l[idx][i].all_qubits - for qb in sublist] + qubitids = [qb.id for sublist in self._l[idx][i].all_qubits for qb in sublist] gid = self._get_gate_indices(idx, i, qubitids) # check that there are no other gates between this and its # inverse on any of the other qubits involved erase = True for j in range(len(qubitids)): - erase *= (inv == self._l[qubitids[j]][gid[j] + 1]) + erase *= inv == self._l[qubitids[j]][gid[j] + 1] # drop these two gates if possible and goto next iteration if erase: for j in range(len(qubitids)): - new_list = (self._l[qubitids[j]][0:gid[j]] + - self._l[qubitids[j]][gid[j] + 2:]) + new_list = ( + self._l[qubitids[j]][0 : gid[j]] # noqa: E203 + + self._l[qubitids[j]][gid[j] + 2 :] # noqa: E203 + ) self._l[qubitids[j]] = new_list i = 0 limit -= 2 @@ -171,24 +163,23 @@ def _optimize(self, idx, lim=None): # gates are not each other's inverses --> check if they're # mergeable try: - merged_command = self._l[idx][i].get_merged( - self._l[idx][i + 1]) + merged_command = self._l[idx][i].get_merged(self._l[idx][i + 1]) # determine index of this gate on all qubits - qubitids = [qb.id for sublist in self._l[idx][i].all_qubits - for qb in sublist] + qubitids = [qb.id for sublist in self._l[idx][i].all_qubits for qb in sublist] gid = self._get_gate_indices(idx, i, qubitids) merge = True for j in range(len(qubitids)): - m = self._l[qubitids[j]][gid[j]].get_merged( - self._l[qubitids[j]][gid[j] + 1]) - merge *= (m == merged_command) + m = self._l[qubitids[j]][gid[j]].get_merged(self._l[qubitids[j]][gid[j] + 1]) + merge *= m == merged_command if merge: for j in range(len(qubitids)): self._l[qubitids[j]][gid[j]] = merged_command - new_list = (self._l[qubitids[j]][0:gid[j] + 1] + - self._l[qubitids[j]][gid[j] + 2:]) + new_list = ( + self._l[qubitids[j]][0 : gid[j] + 1] # noqa: E203 + + self._l[qubitids[j]][gid[j] + 2 :] # noqa: E203 + ) self._l[qubitids[j]] = new_list i = 0 limit -= 1 @@ -205,15 +196,15 @@ def _check_and_send(self): optimize the pipeline and then send it on. """ for i in self._l: - if (len(self._l[i]) >= self._m or len(self._l[i]) > 0 and - isinstance(self._l[i][-1].gate, FastForwardingGate)): + if ( + len(self._l[i]) >= self._m + or len(self._l[i]) > 0 + and isinstance(self._l[i][-1].gate, FastForwardingGate) + ): self._optimize(i) - if (len(self._l[i]) >= self._m and not - isinstance(self._l[i][-1].gate, - FastForwardingGate)): + if len(self._l[i]) >= self._m and not isinstance(self._l[i][-1].gate, FastForwardingGate): self._send_qubit_pipeline(i, len(self._l[i]) - self._m + 1) - elif (len(self._l[i]) > 0 and - isinstance(self._l[i][-1].gate, FastForwardingGate)): + elif len(self._l[i]) > 0 and isinstance(self._l[i][-1].gate, FastForwardingGate): self._send_qubit_pipeline(i, len(self._l[i])) new_dict = dict() for idx in self._l: diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index 121dbb471..f4cc651ff 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,16 +12,21 @@ # 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._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, - FastForwardingGate, ClassicalInstructionGate) +from projectq.ops import ( + CNOT, + H, + Rx, + Ry, + AllocateQubitGate, + X, + FastForwardingGate, + ClassicalInstructionGate, +) from projectq.cengines import _optimize @@ -104,8 +110,7 @@ def test_local_optimizer_cancel_inverse(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 2 assert received_commands[0].gate == H @@ -139,8 +144,8 @@ def test_local_optimizer_identity_gates(): for _ in range(10): Rx(0.0) | qb0 Ry(0.0) | qb0 - Rx(4*math.pi) | qb0 - Ry(4*math.pi) | qb0 + Rx(4 * math.pi) | qb0 + Ry(4 * math.pi) | qb0 Rx(0.5) | qb0 assert len(backend.received_commands) == 0 eng.flush() diff --git a/projectq/cengines/_replacer/__init__.py b/projectq/cengines/_replacer/__init__.py index d1d7ba9a8..bae476ff6 100755 --- a/projectq/cengines/_replacer/__init__.py +++ b/projectq/cengines/_replacer/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +15,4 @@ from ._decomposition_rule import DecompositionRule, ThisIsNotAGateClassError from ._decomposition_rule_set import DecompositionRuleSet -from ._replacer import (AutoReplacer, - InstructionFilter, - NoGateDecompositionError) +from ._replacer import AutoReplacer, InstructionFilter, NoGateDecompositionError diff --git a/projectq/cengines/_replacer/_decomposition_rule.py b/projectq/cengines/_replacer/_decomposition_rule.py index d742f24c5..b53016eed 100755 --- a/projectq/cengines/_replacer/_decomposition_rule.py +++ b/projectq/cengines/_replacer/_decomposition_rule.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,10 +25,7 @@ class DecompositionRule: A rule for breaking down specific gates into sequences of simpler gates. """ - def __init__(self, - gate_class, - gate_decomposer, - gate_recognizer=lambda cmd: True): + def __init__(self, gate_class, gate_decomposer, gate_recognizer=lambda cmd: True): """ Args: gate_class (type): The type of gate that this rule decomposes. @@ -60,11 +58,13 @@ def __init__(self, if isinstance(gate_class, BasicGate): raise ThisIsNotAGateClassError( "gate_class is a gate instance instead of a type of BasicGate." - "\nDid you pass in someGate instead of someGate.__class__?") + "\nDid you pass in someGate instead of someGate.__class__?" + ) if gate_class == type.__class__: raise ThisIsNotAGateClassError( "gate_class is type.__class__ instead of a type of BasicGate." - "\nDid you pass in GateType.__class__ instead of GateType?") + "\nDid you pass in GateType.__class__ instead of GateType?" + ) self.gate_class = gate_class self.gate_decomposer = gate_decomposer diff --git a/projectq/cengines/_replacer/_decomposition_rule_set.py b/projectq/cengines/_replacer/_decomposition_rule_set.py index d7d1a38cb..45c004e6c 100755 --- a/projectq/cengines/_replacer/_decomposition_rule_set.py +++ b/projectq/cengines/_replacer/_decomposition_rule_set.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +20,7 @@ class DecompositionRuleSet: """ A collection of indexed decomposition rules. """ + def __init__(self, rules=None, modules=None): """ Args: @@ -33,10 +35,9 @@ def __init__(self, rules=None, modules=None): self.add_decomposition_rules(rules) if modules: - self.add_decomposition_rules([ - rule - for module in modules - for rule in module.all_defined_decomposition_rules]) + self.add_decomposition_rules( + [rule for module in modules for rule in module.all_defined_decomposition_rules] + ) def add_decomposition_rules(self, rules): for rule in rules: @@ -56,11 +57,12 @@ def add_decomposition_rule(self, rule): self.decompositions[cls].append(decomp_obj) -class ModuleWithDecompositionRuleSet: +class ModuleWithDecompositionRuleSet: # pragma: no cover """ Interface type for explaining one of the parameters that can be given to DecompositionRuleSet. """ + def __init__(self, all_defined_decomposition_rules): """ Args: @@ -75,6 +77,7 @@ class _Decomposition(object): The Decomposition class can be used to register a decomposition rule (by calling register_decomposition) """ + def __init__(self, replacement_fun, recogn_fun): """ Construct the Decomposition object. @@ -132,6 +135,7 @@ def get_inverse_decomposition(self): Returns: Decomposition handling the inverse of the original command. """ + def decomp(cmd): with Dagger(cmd.engine): self.decompose(cmd.get_inverse()) diff --git a/projectq/cengines/_replacer/_decomposition_rule_test.py b/projectq/cengines/_replacer/_decomposition_rule_test.py index a162735ae..c2ac1f75e 100755 --- a/projectq/cengines/_replacer/_decomposition_rule_test.py +++ b/projectq/cengines/_replacer/_decomposition_rule_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.cengines._replacer._decomposition_rule.py.""" import pytest @@ -25,11 +25,7 @@ class WrongInput(BasicRotationGate): pass with pytest.raises(ThisIsNotAGateClassError): - _ = DecompositionRule(WrongInput.__class__, - lambda cmd: None, - lambda cmd: None) + _ = DecompositionRule(WrongInput.__class__, lambda cmd: None, lambda cmd: None) with pytest.raises(ThisIsNotAGateClassError): - _ = DecompositionRule(WrongInput(0), - lambda cmd: None, - lambda cmd: None) + _ = DecompositionRule(WrongInput(0), lambda cmd: None, lambda cmd: None) diff --git a/projectq/cengines/_replacer/_replacer.py b/projectq/cengines/_replacer/_replacer.py index 85fa303dc..0ad153297 100755 --- a/projectq/cengines/_replacer/_replacer.py +++ b/projectq/cengines/_replacer/_replacer.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Contains an AutoReplacer compiler engine which uses engine.is_available to determine whether a command can be executed. If not, it uses the loaded setup @@ -21,11 +21,8 @@ replace/keep. """ -from projectq.cengines import (BasicEngine, - ForwarderEngine, - CommandModifier) -from projectq.ops import (FlushGate, - get_inverse) +from projectq.cengines import BasicEngine, ForwarderEngine, CommandModifier +from projectq.ops import FlushGate, get_inverse class NoGateDecompositionError(Exception): @@ -39,6 +36,7 @@ class InstructionFilter(BasicEngine): this function, which then returns whether this command can be executed (True) or needs replacement (False). """ + def __init__(self, filterfun): """ Initializer: The provided filterfun returns True for all commands @@ -79,9 +77,12 @@ class AutoReplacer(BasicEngine): further. The loaded setup is used to find decomposition rules appropriate for each command (e.g., setups.default). """ - def __init__(self, decompositionRuleSet, - decomposition_chooser=lambda cmd, - decomposition_list: decomposition_list[0]): + + def __init__( + self, + decompositionRuleSet, + decomposition_chooser=lambda cmd, decomposition_list: decomposition_list[0], + ): """ Initialize an AutoReplacer. @@ -156,10 +157,7 @@ def _process_command(self, cmd): if level < len(inverse_mro): inv_class_name = inverse_mro[level].__name__ try: - potential_decomps += [ - d.get_inverse_decomposition() - for d in rules[inv_class_name] - ] + potential_decomps += [d.get_inverse_decomposition() for d in rules[inv_class_name]] except KeyError: pass # throw out the ones which don't recognize the command @@ -170,8 +168,7 @@ def _process_command(self, cmd): break if len(decomp_list) == 0: - raise NoGateDecompositionError("\nNo replacement found for " + - str(cmd) + "!") + raise NoGateDecompositionError("\nNo replacement found for " + str(cmd) + "!") # use decomposition chooser to determine the best decomposition chosen_decomp = self._decomp_chooser(cmd, decomp_list) @@ -185,6 +182,7 @@ def cmd_mod_fun(cmd): # Adds the tags cmd.tags = old_tags[:] + cmd.tags cmd.engine = self.main_engine return cmd + # the CommandModifier calls cmd_mod_fun for each command # --> commands get the right tags. cmod_eng = CommandModifier(cmd_mod_fun) diff --git a/projectq/cengines/_replacer/_replacer_test.py b/projectq/cengines/_replacer/_replacer_test.py index a27b5557c..a63e43c87 100755 --- a/projectq/cengines/_replacer/_replacer_test.py +++ b/projectq/cengines/_replacer/_replacer_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,17 +12,21 @@ # 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._replacer._replacer.py.""" import pytest from projectq import MainEngine -from projectq.cengines import (DummyEngine, - DecompositionRuleSet, - DecompositionRule) -from projectq.ops import (BasicGate, ClassicalInstructionGate, Command, H, - NotInvertible, Rx, Ry, S, X) +from projectq.cengines import DummyEngine, DecompositionRuleSet, DecompositionRule +from projectq.ops import ( + BasicGate, + ClassicalInstructionGate, + Command, + H, + NotInvertible, + Rx, + X, +) from projectq.cengines._replacer import _replacer @@ -30,6 +35,7 @@ def my_filter(self, cmd): if cmd.gate == H: return True return False + filter_eng = _replacer.InstructionFilter(my_filter) eng = MainEngine(backend=DummyEngine(), engine_list=[filter_eng]) qubit = eng.allocate_qubit() @@ -42,7 +48,8 @@ def my_filter(self, cmd): class SomeGateClass(BasicGate): - """ Test gate class """ + """Test gate class""" + pass @@ -63,21 +70,18 @@ def decompose_test1(cmd): def recognize_test(cmd): return True - result.add_decomposition_rule( - DecompositionRule(SomeGate.__class__, decompose_test1, - recognize_test)) + result.add_decomposition_rule(DecompositionRule(SomeGate.__class__, decompose_test1, recognize_test)) def decompose_test2(cmd): qb = cmd.qubits H | qb - result.add_decomposition_rule( - DecompositionRule(SomeGateClass, decompose_test2, - recognize_test)) + result.add_decomposition_rule(DecompositionRule(SomeGateClass, decompose_test2, recognize_test)) assert len(result.decompositions[SomeGate.__class__.__name__]) == 2 return result + rule_set = make_decomposition_rule_set() @@ -88,15 +92,17 @@ def test_gate_filter_func(self, cmd): if cmd.gate == SomeGate: return False return True + return _replacer.InstructionFilter(test_gate_filter_func) def test_auto_replacer_default_chooser(fixture_gate_filter): # Test that default decomposition_chooser takes always first rule. backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_replacer.AutoReplacer(rule_set), - fixture_gate_filter]) + eng = MainEngine( + backend=backend, + engine_list=[_replacer.AutoReplacer(rule_set), fixture_gate_filter], + ) assert len(rule_set.decompositions[SomeGate.__class__.__name__]) == 2 assert len(backend.received_commands) == 0 qb = eng.allocate_qubit() @@ -110,11 +116,15 @@ def test_auto_replacer_decomposition_chooser(fixture_gate_filter): # Supply a decomposition chooser which always chooses last rule. def test_decomp_chooser(cmd, decomposition_list): return decomposition_list[-1] + backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_replacer.AutoReplacer(rule_set, - test_decomp_chooser), - fixture_gate_filter]) + eng = MainEngine( + backend=backend, + engine_list=[ + _replacer.AutoReplacer(rule_set, test_decomp_chooser), + fixture_gate_filter, + ], + ) assert len(rule_set.decompositions[SomeGate.__class__.__name__]) == 2 assert len(backend.received_commands) == 0 qb = eng.allocate_qubit() @@ -131,10 +141,10 @@ def h_filter(self, cmd): if cmd.gate == H: return False return True + h_filter = _replacer.InstructionFilter(h_filter) backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_replacer.AutoReplacer(rule_set), h_filter]) + eng = MainEngine(backend=backend, engine_list=[_replacer.AutoReplacer(rule_set), h_filter]) qubit = eng.allocate_qubit() with pytest.raises(_replacer.NoGateDecompositionError): H | qubit @@ -161,9 +171,7 @@ def decompose_no_magic_gate(cmd): def recognize_no_magic_gate(cmd): return True - rule_set.add_decomposition_rule(DecompositionRule(NoMagicGate, - decompose_no_magic_gate, - recognize_no_magic_gate)) + rule_set.add_decomposition_rule(DecompositionRule(NoMagicGate, decompose_no_magic_gate, recognize_no_magic_gate)) def magic_filter(self, cmd): if cmd.gate == MagicGate(): @@ -171,9 +179,13 @@ def magic_filter(self, cmd): return True backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_replacer.AutoReplacer(rule_set), - _replacer.InstructionFilter(magic_filter)]) + eng = MainEngine( + backend=backend, + engine_list=[ + _replacer.AutoReplacer(rule_set), + _replacer.InstructionFilter(magic_filter), + ], + ) assert len(backend.received_commands) == 0 qb = eng.allocate_qubit() MagicGate() | qb @@ -186,9 +198,10 @@ def magic_filter(self, cmd): def test_auto_replacer_adds_tags(fixture_gate_filter): # Test that AutoReplacer puts back the tags backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_replacer.AutoReplacer(rule_set), - fixture_gate_filter]) + eng = MainEngine( + backend=backend, + engine_list=[_replacer.AutoReplacer(rule_set), fixture_gate_filter], + ) assert len(rule_set.decompositions[SomeGate.__class__.__name__]) == 2 assert len(backend.received_commands) == 0 qb = eng.allocate_qubit() @@ -207,16 +220,13 @@ class DerivedSomeGate(SomeGateClass): pass def test_gate_filter_func(self, cmd): - if (cmd.gate == X or cmd.gate == H or - isinstance(cmd.gate, ClassicalInstructionGate)): + if cmd.gate == X or cmd.gate == H or isinstance(cmd.gate, ClassicalInstructionGate): return True return False i_filter = _replacer.InstructionFilter(test_gate_filter_func) backend = DummyEngine(save_commands=True) - eng = MainEngine(backend=backend, - engine_list=[_replacer.AutoReplacer(rule_set), - i_filter]) + eng = MainEngine(backend=backend, engine_list=[_replacer.AutoReplacer(rule_set), i_filter]) qb = eng.allocate_qubit() DerivedSomeGate() | qb eng.flush() diff --git a/projectq/cengines/_swapandcnotflipper.py b/projectq/cengines/_swapandcnotflipper.py index e3da39ff3..481dd7365 100755 --- a/projectq/cengines/_swapandcnotflipper.py +++ b/projectq/cengines/_swapandcnotflipper.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Contains a compiler engine which flips the directionality of CNOTs according to the given connectivity graph. It also translates Swap gates to CNOTs if @@ -19,15 +19,9 @@ """ from copy import deepcopy -from projectq.cengines import (BasicEngine, - ForwarderEngine, - CommandModifier) +from projectq.cengines import BasicEngine, ForwarderEngine, CommandModifier from projectq.meta import get_control_count -from projectq.ops import (All, - NOT, - CNOT, - H, - Swap) +from projectq.ops import All, NOT, CNOT, H, Swap class SwapAndCNOTFlipper(BasicEngine): @@ -72,8 +66,7 @@ def _is_cnot(self, cmd): Args: cmd (Command): Command to check """ - return (isinstance(cmd.gate, NOT.__class__) and - get_control_count(cmd) == 1) + return isinstance(cmd.gate, NOT.__class__) and get_control_count(cmd) == 1 def _is_swap(self, cmd): """ @@ -82,7 +75,7 @@ def _is_swap(self, cmd): Args: cmd (Command): Command to check """ - return (get_control_count(cmd) == 0 and cmd.gate == Swap) + return get_control_count(cmd) == 0 and cmd.gate == Swap def _needs_flipping(self, cmd): """ @@ -98,9 +91,7 @@ def _needs_flipping(self, cmd): control = cmd.control_qubits[0].id is_possible = (control, target) in self.connectivity if not is_possible and (target, control) not in self.connectivity: - raise RuntimeError("The provided connectivity does not " - "allow to execute the CNOT gate {}." - .format(str(cmd))) + raise RuntimeError("The provided connectivity does not allow to execute the CNOT gate {}.".format(str(cmd))) return not is_possible def _send_cnot(self, cmd, control, target, flip=False): @@ -108,6 +99,7 @@ def cmd_mod(command): command.tags = deepcopy(cmd.tags) + command.tags command.engine = self.main_engine return command + # We'll have to add all meta tags before sending on cmd_mod_eng = CommandModifier(cmd_mod) cmd_mod_eng.next_engine = self.next_engine @@ -149,9 +141,9 @@ def receive(self, command_list): control = [qubits[1]] target = [qubits[0]] else: - raise RuntimeError("The provided connectivity does not " - "allow to execute the Swap gate {}." - .format(str(cmd))) + raise RuntimeError( + "The provided connectivity does not allow to execute the Swap gate {}.".format(str(cmd)) + ) self._send_cnot(cmd, control, target) self._send_cnot(cmd, target, control, True) self._send_cnot(cmd, control, target) diff --git a/projectq/cengines/_swapandcnotflipper_test.py b/projectq/cengines/_swapandcnotflipper_test.py index b59b88e9b..fdea4fdd7 100755 --- a/projectq/cengines/_swapandcnotflipper_test.py +++ b/projectq/cengines/_swapandcnotflipper_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,18 +12,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.cengines._swapandcnotflipper.py.""" import pytest from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import All, H, CNOT, X, Measure, Swap -from projectq.meta import (Control, Compute, Uncompute, ComputeTag, - UncomputeTag) +from projectq.ops import All, H, CNOT, X, Swap +from projectq.meta import Control, Compute, Uncompute, ComputeTag, UncomputeTag from projectq.cengines import _swapandcnotflipper -from projectq.backends import IBMBackend def test_swapandcnotflipper_missing_connection(): diff --git a/projectq/cengines/_tagremover.py b/projectq/cengines/_tagremover.py index bdfc5b86a..213f2ed6c 100755 --- a/projectq/cengines/_tagremover.py +++ b/projectq/cengines/_tagremover.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Contains a TagRemover engine, which removes temporary command tags (such as Compute/Uncompute), thus enabling optimization across meta statements (loops @@ -30,6 +30,7 @@ class TagRemover(BasicEngine): order to enable optimizations across meta-function boundaries (compute/ action/uncompute or loops after unrolling) """ + def __init__(self, tags=[ComputeTag, UncomputeTag]): """ Construct the TagRemover. diff --git a/projectq/cengines/_tagremover_test.py b/projectq/cengines/_tagremover_test.py index 1318b7bc9..9f84fce7e 100755 --- a/projectq/cengines/_tagremover_test.py +++ b/projectq/cengines/_tagremover_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.cengines._tagremover.py.""" from projectq import MainEngine diff --git a/projectq/cengines/_testengine.py b/projectq/cengines/_testengine.py index cf992fcda..106cee637 100755 --- a/projectq/cengines/_testengine.py +++ b/projectq/cengines/_testengine.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,13 +12,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. - """TestEngine and DummyEngine.""" - from copy import deepcopy from projectq.cengines import BasicEngine -from projectq.ops import FlushGate, Allocate, Deallocate +from projectq.ops import FlushGate class CompareEngine(BasicEngine): @@ -26,6 +25,7 @@ class CompareEngine(BasicEngine): for testing purposes. Two CompareEngine backends can be compared and return True if they contain the same commmands. """ + def __init__(self): BasicEngine.__init__(self) self._l = [[]] @@ -35,14 +35,13 @@ def is_available(self, cmd): def cache_cmd(self, cmd): # are there qubit ids that haven't been added to the list? - all_qubit_id_list = [qubit.id for qureg in cmd.all_qubits - for qubit in qureg] + all_qubit_id_list = [qubit.id for qureg in cmd.all_qubits for qubit in qureg] maxidx = int(0) for qubit_id in all_qubit_id_list: maxidx = max(maxidx, qubit_id) # if so, increase size of list to account for these qubits - add = maxidx+1-len(self._l) + add = maxidx + 1 - len(self._l) if add > 0: for i in range(add): self._l += [[]] @@ -64,8 +63,7 @@ def compare_cmds(self, c1, c2): return c1 == c2 def __eq__(self, other): - if (not isinstance(other, CompareEngine) or - len(self._l) != len(other._l)): + if not isinstance(other, CompareEngine) or len(self._l) != len(other._l): return False for i in range(len(self._l)): if len(self._l[i]) != len(other._l[i]): @@ -98,6 +96,7 @@ class DummyEngine(BasicEngine): list in self.received_commands. Elements are appended to this list so they are ordered according to when they are received. """ + def __init__(self, save_commands=False): """ Initialize DummyEngine diff --git a/projectq/cengines/_testengine_test.py b/projectq/cengines/_testengine_test.py index 1a7d5bab8..baf4010de 100755 --- a/projectq/cengines/_testengine_test.py +++ b/projectq/cengines/_testengine_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.cengines._testengine.py.""" from projectq import MainEngine @@ -29,9 +29,11 @@ def test_compare_engine_str(): H | qb0 CNOT | (qb0, qb1) eng.flush() - expected = ("Qubit 0 : Allocate | Qureg[0], H | Qureg[0], " + - "CX | ( Qureg[0], Qureg[1] )\nQubit 1 : Allocate | Qureg[1]," + - " CX | ( Qureg[0], Qureg[1] )\n") + expected = ( + "Qubit 0 : Allocate | Qureg[0], H | Qureg[0], " + + "CX | ( Qureg[0], Qureg[1] )\nQubit 1 : Allocate | Qureg[1]," + + " CX | ( Qureg[0], Qureg[1] )\n" + ) assert str(compare_engine) == expected @@ -97,9 +99,9 @@ def test_compare_engine(): CNOT | (qb20, qb21) eng2.flush() # test other branch to fail - qb30 = eng3.allocate_qubit() - qb31 = eng3.allocate_qubit() - qb32 = eng3.allocate_qubit() + qb30 = eng3.allocate_qubit() # noqa: F841 + qb31 = eng3.allocate_qubit() # noqa: F841 + qb32 = eng3.allocate_qubit() # noqa: F841 eng3.flush() assert compare_engine0 == compare_engine1 assert compare_engine1 != compare_engine2 diff --git a/projectq/cengines/_twodmapper.py b/projectq/cengines/_twodmapper.py index 769dbedd8..2c068c4ad 100644 --- a/projectq/cengines/_twodmapper.py +++ b/projectq/cengines/_twodmapper.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Mapper for a quantum circuit to a 2D square grid. @@ -29,11 +29,15 @@ import networkx as nx -from projectq.cengines import (BasicMapperEngine, LinearMapper, - return_swap_depth) +from projectq.cengines import BasicMapperEngine, LinearMapper, return_swap_depth from projectq.meta import LogicalQubitIDTag -from projectq.ops import (AllocateQubitGate, Command, DeallocateQubitGate, - FlushGate, Swap) +from projectq.ops import ( + AllocateQubitGate, + Command, + DeallocateQubitGate, + FlushGate, + Swap, +) from projectq.types import WeakQubitRef @@ -75,10 +79,16 @@ class GridMapper(BasicMapperEngine): mappings which have been applied """ - def __init__(self, num_rows, num_columns, mapped_ids_to_backend_ids=None, - storage=1000, - optimization_function=lambda x: return_swap_depth(x), - num_optimization_steps=50): + + def __init__( + self, + num_rows, + num_columns, + mapped_ids_to_backend_ids=None, + storage=1000, + optimization_function=lambda x: return_swap_depth(x), + num_optimization_steps=50, + ): """ Initialize a GridMapper compiler engine. @@ -116,10 +126,9 @@ def __init__(self, num_rows, num_columns, mapped_ids_to_backend_ids=None, self._mapped_ids_to_backend_ids = dict() for i in range(self.num_qubits): self._mapped_ids_to_backend_ids[i] = i - if (not (set(self._mapped_ids_to_backend_ids.keys()) == - set(list(range(self.num_qubits)))) or not ( - len(set(self._mapped_ids_to_backend_ids.values())) == - self.num_qubits)): + if not (set(self._mapped_ids_to_backend_ids.keys()) == set(range(self.num_qubits))) or not ( + len(set(self._mapped_ids_to_backend_ids.values())) == self.num_qubits + ): raise RuntimeError("Incorrect mapped_ids_to_backend_ids parameter") self._backend_ids_to_mapped_ids = dict() for mapped_id, backend_id in self._mapped_ids_to_backend_ids.items(): @@ -155,8 +164,7 @@ def __init__(self, num_rows, num_columns, mapped_ids_to_backend_ids=None, self._map_1d_to_2d[mapped_id] = mapped_id else: mapped_id_2d = row_index * self.num_columns + column_index - mapped_id_1d = ((row_index + 1) * self.num_columns - - column_index - 1) + mapped_id_1d = (row_index + 1) * self.num_columns - column_index - 1 self._map_2d_to_1d[mapped_id_2d] = mapped_id_1d self._map_1d_to_2d[mapped_id_1d] = mapped_id_2d # Statistics: @@ -176,8 +184,7 @@ def current_mapping(self, current_mapping): else: self._current_row_major_mapping = dict() for logical_id, backend_id in current_mapping.items(): - self._current_row_major_mapping[logical_id] = ( - self._backend_ids_to_mapped_ids[backend_id]) + self._current_row_major_mapping[logical_id] = self._backend_ids_to_mapped_ids[backend_id] def is_available(self, cmd): """ @@ -209,8 +216,7 @@ def _return_new_mapping(self): # Change old mapping to 1D in order to use LinearChain heuristic if self._current_row_major_mapping: old_mapping_1d = dict() - for logical_id, mapped_id in ( - self._current_row_major_mapping.items()): + for logical_id, mapped_id in self._current_row_major_mapping.items(): old_mapping_1d[logical_id] = self._map_2d_to_1d[mapped_id] else: old_mapping_1d = self._current_row_major_mapping @@ -220,7 +226,8 @@ def _return_new_mapping(self): cyclic=False, currently_allocated_ids=self._currently_allocated_ids, stored_commands=self._stored_commands, - current_mapping=old_mapping_1d) + current_mapping=old_mapping_1d, + ) new_mapping_2d = dict() for logical_id, mapped_id in new_mapping_1d.items(): @@ -233,10 +240,8 @@ def _compare_and_swap(self, element0, element1, key): so that key(element0) < key(element1) """ if key(element0) > key(element1): - mapped_id0 = (element0.current_column + - element0.current_row * self.num_columns) - mapped_id1 = (element1.current_column + - element1.current_row * self.num_columns) + mapped_id0 = element0.current_column + element0.current_row * self.num_columns + mapped_id1 = element1.current_column + element1.current_row * self.num_columns swap_operation = (mapped_id0, mapped_id1) # swap elements but update also current position: tmp_0 = element0.final_row @@ -258,16 +263,16 @@ def _sort_within_rows(self, final_positions, key): finished_sorting = False while not finished_sorting: finished_sorting = True - for column in range(1, self.num_columns-1, 2): + for column in range(1, self.num_columns - 1, 2): element0 = final_positions[row][column] - element1 = final_positions[row][column+1] + element1 = final_positions[row][column + 1] swap = self._compare_and_swap(element0, element1, key=key) if swap is not None: finished_sorting = False swap_operations.append(swap) - for column in range(0, self.num_columns-1, 2): + for column in range(0, self.num_columns - 1, 2): element0 = final_positions[row][column] - element1 = final_positions[row][column+1] + element1 = final_positions[row][column + 1] swap = self._compare_and_swap(element0, element1, key=key) if swap is not None: finished_sorting = False @@ -280,16 +285,16 @@ def _sort_within_columns(self, final_positions, key): finished_sorting = False while not finished_sorting: finished_sorting = True - for row in range(1, self.num_rows-1, 2): + for row in range(1, self.num_rows - 1, 2): element0 = final_positions[row][column] - element1 = final_positions[row+1][column] + element1 = final_positions[row + 1][column] swap = self._compare_and_swap(element0, element1, key=key) if swap is not None: finished_sorting = False swap_operations.append(swap) - for row in range(0, self.num_rows-1, 2): + for row in range(0, self.num_rows - 1, 2): element0 = final_positions[row][column] - element1 = final_positions[row+1][column] + element1 = final_positions[row + 1][column] swap = self._compare_and_swap(element0, element1, key=key) if swap is not None: finished_sorting = False @@ -317,9 +322,16 @@ def return_swaps(self, old_mapping, new_mapping, permutation=None): swap_operations = [] class Position(object): - """ Custom Container.""" - def __init__(self, current_row, current_column, final_row, - final_column, row_after_step_1=None): + """Custom Container.""" + + def __init__( + self, + current_row, + current_column, + final_row, + final_column, + row_after_step_1=None, + ): self.current_row = current_row self.current_column = current_column self.final_row = final_row @@ -329,8 +341,7 @@ def __init__(self, current_row, current_column, final_row, # final_positions contains info containers # final_position[i][j] contains info container with # current_row == i and current_column == j - final_positions = [[None for i in range(self.num_columns)] - for j in range(self.num_rows)] + final_positions = [[None for i in range(self.num_columns)] for j in range(self.num_rows)] # move qubits which are in both mappings used_mapped_ids = set() for logical_id in old_mapping: @@ -340,10 +351,12 @@ def __init__(self, current_row, current_column, final_row, old_row = old_mapping[logical_id] // self.num_columns new_column = new_mapping[logical_id] % self.num_columns new_row = new_mapping[logical_id] // self.num_columns - info_container = Position(current_row=old_row, - current_column=old_column, - final_row=new_row, - final_column=new_column) + info_container = Position( + current_row=old_row, + current_column=old_column, + final_row=new_row, + final_column=new_column, + ) final_positions[old_row][old_column] = info_container # exchange all remaining None with the not yet used mapped ids all_ids = set(range(self.num_qubits)) @@ -355,10 +368,12 @@ def __init__(self, current_row, current_column, final_row, mapped_id = not_used_mapped_ids.pop() new_column = mapped_id % self.num_columns new_row = mapped_id // self.num_columns - info_container = Position(current_row=row, - current_column=column, - final_row=new_row, - final_column=new_column) + info_container = Position( + current_row=row, + current_column=column, + final_row=new_row, + final_column=new_column, + ) final_positions[row][column] = info_container assert len(not_used_mapped_ids) == 0 # 1. Assign column_after_step_1 for each element @@ -370,8 +385,7 @@ def __init__(self, current_row, current_column, final_row, graph = nx.Graph() offset = self.num_columns graph.add_nodes_from(range(self.num_columns), bipartite=0) - graph.add_nodes_from(range(offset, offset + self.num_columns), - bipartite=1) + graph.add_nodes_from(range(offset, offset + self.num_columns), bipartite=1) # Add an edge to the graph from (i, j+offset) for every element # currently in column i which should go to column j for the new # mapping @@ -416,16 +430,13 @@ def __init__(self, current_row, current_column, final_row, best_element = element best_element.row_after_step_1 = row_after_step_1 # 2. Sort inside all the rows - swaps = self._sort_within_columns(final_positions=final_positions, - key=lambda x: x.row_after_step_1) + swaps = self._sort_within_columns(final_positions=final_positions, key=lambda x: x.row_after_step_1) swap_operations += swaps # 3. Sort inside all the columns - swaps = self._sort_within_rows(final_positions=final_positions, - key=lambda x: x.final_column) + swaps = self._sort_within_rows(final_positions=final_positions, key=lambda x: x.final_column) swap_operations += swaps # 4. Sort inside all the rows - swaps = self._sort_within_columns(final_positions=final_positions, - key=lambda x: x.final_row) + swaps = self._sort_within_columns(final_positions=final_positions, key=lambda x: x.final_row) swap_operations += swaps return swap_operations @@ -451,31 +462,27 @@ def _send_possible_commands(self): if cmd.qubits[0][0].id in self._current_row_major_mapping: self._currently_allocated_ids.add(cmd.qubits[0][0].id) - mapped_id = self._current_row_major_mapping[ - cmd.qubits[0][0].id] - qb = WeakQubitRef( - engine=self, - idx=self._mapped_ids_to_backend_ids[mapped_id]) + mapped_id = self._current_row_major_mapping[cmd.qubits[0][0].id] + qb = WeakQubitRef(engine=self, idx=self._mapped_ids_to_backend_ids[mapped_id]) new_cmd = Command( engine=self, gate=AllocateQubitGate(), qubits=([qb],), - tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)]) + tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)], + ) self.send([new_cmd]) else: new_stored_commands.append(cmd) elif isinstance(cmd.gate, DeallocateQubitGate): if cmd.qubits[0][0].id in active_ids: - mapped_id = self._current_row_major_mapping[ - cmd.qubits[0][0].id] - qb = WeakQubitRef( - engine=self, - idx=self._mapped_ids_to_backend_ids[mapped_id]) + mapped_id = self._current_row_major_mapping[cmd.qubits[0][0].id] + qb = WeakQubitRef(engine=self, idx=self._mapped_ids_to_backend_ids[mapped_id]) new_cmd = Command( engine=self, gate=DeallocateQubitGate(), qubits=([qb],), - tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)]) + tags=[LogicalQubitIDTag(cmd.qubits[0][0].id)], + ) self._currently_allocated_ids.remove(cmd.qubits[0][0].id) active_ids.remove(cmd.qubits[0][0].id) self._current_row_major_mapping.pop(cmd.qubits[0][0].id) @@ -491,8 +498,7 @@ def _send_possible_commands(self): if qubit.id not in active_ids: send_gate = False break - mapped_ids.add( - self._current_row_major_mapping[qubit.id]) + mapped_ids.add(self._current_row_major_mapping[qubit.id]) # Check that mapped ids are nearest neighbour on 2D grid if len(mapped_ids) == 2: qb0, qb1 = sorted(list(mapped_ids)) @@ -537,18 +543,17 @@ def _run(self): lowest_cost = None matchings_numbers = list(range(self.num_rows)) if self.num_optimization_steps <= math.factorial(self.num_rows): - permutations = itertools.permutations(matchings_numbers, - self.num_rows) + permutations = itertools.permutations(matchings_numbers, self.num_rows) else: permutations = [] for _ in range(self.num_optimization_steps): - permutations.append(self._rng.sample(matchings_numbers, - self.num_rows)) + permutations.append(self._rng.sample(matchings_numbers, self.num_rows)) for permutation in permutations: trial_swaps = self.return_swaps( old_mapping=self._current_row_major_mapping, new_mapping=new_row_major_mapping, - permutation=permutation) + permutation=permutation, + ) if swaps is None: swaps = trial_swaps lowest_cost = self.optimization_function(trial_swaps) @@ -560,25 +565,16 @@ def _run(self): # i.e., contained in self._currently_allocated_ids) mapped_ids_used = set() for logical_id in self._currently_allocated_ids: - mapped_ids_used.add( - self._current_row_major_mapping[logical_id]) - not_allocated_ids = set(range(self.num_qubits)).difference( - mapped_ids_used) + mapped_ids_used.add(self._current_row_major_mapping[logical_id]) + not_allocated_ids = set(range(self.num_qubits)).difference(mapped_ids_used) for mapped_id in not_allocated_ids: - qb = WeakQubitRef( - engine=self, - idx=self._mapped_ids_to_backend_ids[mapped_id]) - cmd = Command(engine=self, gate=AllocateQubitGate(), - qubits=([qb],)) + qb = WeakQubitRef(engine=self, idx=self._mapped_ids_to_backend_ids[mapped_id]) + cmd = Command(engine=self, gate=AllocateQubitGate(), qubits=([qb],)) self.send([cmd]) # Send swap operations to arrive at new_mapping: for qubit_id0, qubit_id1 in swaps: - q0 = WeakQubitRef( - engine=self, - idx=self._mapped_ids_to_backend_ids[qubit_id0]) - q1 = WeakQubitRef( - engine=self, - idx=self._mapped_ids_to_backend_ids[qubit_id1]) + q0 = WeakQubitRef(engine=self, idx=self._mapped_ids_to_backend_ids[qubit_id0]) + q1 = WeakQubitRef(engine=self, idx=self._mapped_ids_to_backend_ids[qubit_id1]) cmd = Command(engine=self, gate=Swap, qubits=([q0], [q1])) self.send([cmd]) # Register statistics: @@ -597,30 +593,27 @@ def _run(self): mapped_ids_used = set() for logical_id in self._currently_allocated_ids: mapped_ids_used.add(new_row_major_mapping[logical_id]) - not_needed_anymore = set(range(self.num_qubits)).difference( - mapped_ids_used) + not_needed_anymore = set(range(self.num_qubits)).difference(mapped_ids_used) for mapped_id in not_needed_anymore: - qb = WeakQubitRef( - engine=self, - idx=self._mapped_ids_to_backend_ids[mapped_id]) - cmd = Command(engine=self, gate=DeallocateQubitGate(), - qubits=([qb],)) + qb = WeakQubitRef(engine=self, idx=self._mapped_ids_to_backend_ids[mapped_id]) + cmd = Command(engine=self, gate=DeallocateQubitGate(), qubits=([qb],)) self.send([cmd]) # Change to new map: self._current_row_major_mapping = new_row_major_mapping new_mapping = dict() for logical_id, mapped_id in new_row_major_mapping.items(): - new_mapping[logical_id] = ( - self._mapped_ids_to_backend_ids[mapped_id]) + new_mapping[logical_id] = self._mapped_ids_to_backend_ids[mapped_id] self.current_mapping = new_mapping # Send possible gates: self._send_possible_commands() # Check that mapper actually made progress if len(self._stored_commands) == num_of_stored_commands_before: - raise RuntimeError("Mapper is potentially in an infinite loop. " + - "It is likely that the algorithm requires " + - "too many qubits. Increase the number of " + - "qubits for this mapper.") + raise RuntimeError( + "Mapper is potentially in an infinite loop. " + + "It is likely that the algorithm requires " + + "too many qubits. Increase the number of " + + "qubits for this mapper." + ) def receive(self, command_list): """ @@ -633,7 +626,7 @@ def receive(self, command_list): """ for cmd in command_list: if isinstance(cmd.gate, FlushGate): - while(len(self._stored_commands)): + while len(self._stored_commands): self._run() self.send([cmd]) else: diff --git a/projectq/cengines/_twodmapper_test.py b/projectq/cengines/_twodmapper_test.py index 7e4bfa168..a21969a74 100644 --- a/projectq/cengines/_twodmapper_test.py +++ b/projectq/cengines/_twodmapper_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.cengines._2dmapper.py.""" from copy import deepcopy @@ -23,8 +23,7 @@ import projectq from projectq.cengines import DummyEngine, LocalOptimizer from projectq.meta import LogicalQubitIDTag -from projectq.ops import (Allocate, BasicGate, Command, Deallocate, FlushGate, - X) +from projectq.ops import Allocate, BasicGate, Command, Deallocate, FlushGate, X from projectq.types import WeakQubitRef from projectq.cengines import _twodmapper as two_d @@ -48,12 +47,10 @@ def test_is_available(): def test_wrong_init_mapped_ids_to_backend_ids(): with pytest.raises(RuntimeError): test = {0: 1, 1: 0, 2: 2, 3: 3, 4: 4} - two_d.GridMapper(num_rows=2, num_columns=3, - mapped_ids_to_backend_ids=test) + two_d.GridMapper(num_rows=2, num_columns=3, mapped_ids_to_backend_ids=test) with pytest.raises(RuntimeError): test = {0: 1, 1: 0, 2: 2, 3: 3, 4: 4, 5: 2} - two_d.GridMapper(num_rows=2, num_columns=3, - mapped_ids_to_backend_ids=test) + two_d.GridMapper(num_rows=2, num_columns=3, mapped_ids_to_backend_ids=test) def test_resetting_mapping_to_none(): @@ -67,12 +64,23 @@ def test_resetting_mapping_to_none(): @pytest.mark.parametrize("different_backend_ids", [False, True]) def test_return_new_mapping(different_backend_ids): if different_backend_ids: - map_to_backend_ids = {0: 21, 1: 32, 2: 1, 3: 4, 4: 5, 5: 6, 6: 10, - 7: 7, 8: 0, 9: 56, 10: 55, 11: 9} + map_to_backend_ids = { + 0: 21, + 1: 32, + 2: 1, + 3: 4, + 4: 5, + 5: 6, + 6: 10, + 7: 7, + 8: 0, + 9: 56, + 10: 55, + 11: 9, + } else: map_to_backend_ids = None - mapper = two_d.GridMapper(num_rows=4, num_columns=3, - mapped_ids_to_backend_ids=map_to_backend_ids) + mapper = two_d.GridMapper(num_rows=4, num_columns=3, mapped_ids_to_backend_ids=map_to_backend_ids) eng = projectq.MainEngine(DummyEngine(), [mapper]) linear_chain_ids = [33, 22, 11, 2, 3, 0, 6, 7, 9, 12, 4, 88] mapper._stored_commands = [] @@ -82,16 +90,39 @@ def test_return_new_mapping(different_backend_ids): mapper._stored_commands.append(cmd) for i in range(11): qb0 = WeakQubitRef(engine=None, idx=linear_chain_ids[i]) - qb1 = WeakQubitRef(engine=None, idx=linear_chain_ids[i+1]) + qb1 = WeakQubitRef(engine=None, idx=linear_chain_ids[i + 1]) cmd = Command(None, X, qubits=([qb0],), controls=[qb1]) mapper._stored_commands.append(cmd) new_mapping = mapper._return_new_mapping() - possible_solution_1 = {33: 0, 22: 1, 11: 2, 2: 5, 3: 4, 0: 3, 6: 6, 7: 7, - 9: 8, 12: 11, 4: 10, 88: 9} - possible_solution_2 = {88: 0, 4: 1, 12: 2, 9: 5, 7: 4, 6: 3, 0: 6, 3: 7, - 2: 8, 11: 11, 22: 10, 33: 9} - assert (new_mapping == possible_solution_1 or - new_mapping == possible_solution_2) + possible_solution_1 = { + 33: 0, + 22: 1, + 11: 2, + 2: 5, + 3: 4, + 0: 3, + 6: 6, + 7: 7, + 9: 8, + 12: 11, + 4: 10, + 88: 9, + } + possible_solution_2 = { + 88: 0, + 4: 1, + 12: 2, + 9: 5, + 7: 4, + 6: 3, + 0: 6, + 3: 7, + 2: 8, + 11: 11, + 22: 10, + 33: 9, + } + assert new_mapping == possible_solution_1 or new_mapping == possible_solution_2 eng.flush() if different_backend_ids: transformed_sol1 = dict() @@ -100,17 +131,23 @@ def test_return_new_mapping(different_backend_ids): transformed_sol2 = dict() for logical_id, mapped_id in possible_solution_2.items(): transformed_sol2[logical_id] = map_to_backend_ids[mapped_id] - assert (mapper.current_mapping == transformed_sol1 or - mapper.current_mapping == transformed_sol2) + assert mapper.current_mapping == transformed_sol1 or mapper.current_mapping == transformed_sol2 else: - assert (mapper.current_mapping == possible_solution_1 or - mapper.current_mapping == possible_solution_2) - - -@pytest.mark.parametrize("num_rows, num_columns, seed, none_old, none_new", - [(2, 2, 0, 0, 0), (3, 4, 1, 0, 0), (4, 3, 2, 0, 0), - (5, 5, 3, 0, 0), (5, 3, 4, 3, 0), (4, 4, 5, 0, 3), - (6, 6, 7, 2, 3)]) + assert mapper.current_mapping == possible_solution_1 or mapper.current_mapping == possible_solution_2 + + +@pytest.mark.parametrize( + "num_rows, num_columns, seed, none_old, none_new", + [ + (2, 2, 0, 0, 0), + (3, 4, 1, 0, 0), + (4, 3, 2, 0, 0), + (5, 5, 3, 0, 0), + (5, 3, 4, 3, 0), + (4, 4, 5, 0, 3), + (6, 6, 7, 2, 3), + ], +) def test_return_swaps_random(num_rows, num_columns, seed, none_old, none_new): random.seed(seed) num_qubits = num_rows * num_columns @@ -131,16 +168,15 @@ def test_return_swaps_random(num_rows, num_columns, seed, none_old, none_new): for logical_id in new_none_ids: new_mapping.pop(logical_id) - mapper = two_d.GridMapper(num_rows=num_rows, - num_columns=num_columns) + mapper = two_d.GridMapper(num_rows=num_rows, num_columns=num_columns) swaps = mapper.return_swaps(old_mapping, new_mapping) # Check that Swaps are allowed all_allowed_swaps = set() for row in range(num_rows): - for column in range(num_columns-1): + for column in range(num_columns - 1): qb_id = row * num_columns + column all_allowed_swaps.add((qb_id, qb_id + 1)) - for row in range(num_rows-1): + for row in range(num_rows - 1): for column in range(num_columns): qb_id = row * num_columns + column all_allowed_swaps.add((qb_id, qb_id + num_columns)) @@ -161,24 +197,30 @@ def test_return_swaps_random(num_rows, num_columns, seed, none_old, none_new): @pytest.mark.parametrize("different_backend_ids", [False, True]) def test_send_possible_commands(different_backend_ids): if different_backend_ids: - map_to_backend_ids = {0: 21, 1: 32, 2: 1, 3: 4, 4: 5, 5: 6, 6: 10, - 7: 7} + map_to_backend_ids = {0: 21, 1: 32, 2: 1, 3: 4, 4: 5, 5: 6, 6: 10, 7: 7} else: map_to_backend_ids = None - mapper = two_d.GridMapper(num_rows=2, num_columns=4, - mapped_ids_to_backend_ids=map_to_backend_ids) + mapper = two_d.GridMapper(num_rows=2, num_columns=4, mapped_ids_to_backend_ids=map_to_backend_ids) backend = DummyEngine(save_commands=True) backend.is_last_engine = True mapper.next_engine = backend # mapping is identical except 5 <-> 0 if different_backend_ids: - mapper.current_mapping = {0: 6, 1: 32, 2: 1, 3: 4, 4: 5, 5: 21, 6: 10, - 7: 7} + mapper.current_mapping = {0: 6, 1: 32, 2: 1, 3: 4, 4: 5, 5: 21, 6: 10, 7: 7} else: - mapper.current_mapping = {5: 0, 1: 1, 2: 2, 3: 3, 4: 4, 0: 5, 6: 6, - 7: 7} - neighbours = [(5, 1), (1, 2), (2, 3), (4, 0), (0, 6), (6, 7), - (5, 4), (1, 0), (2, 6), (3, 7)] + mapper.current_mapping = {5: 0, 1: 1, 2: 2, 3: 3, 4: 4, 0: 5, 6: 6, 7: 7} + neighbours = [ + (5, 1), + (1, 2), + (2, 3), + (4, 0), + (0, 6), + (6, 7), + (5, 4), + (1, 0), + (2, 6), + (3, 7), + ] for qb0_id, qb1_id in neighbours: qb0 = WeakQubitRef(engine=None, idx=qb0_id) qb1 = WeakQubitRef(engine=None, idx=qb1_id) @@ -188,8 +230,7 @@ def test_send_possible_commands(different_backend_ids): mapper._send_possible_commands() assert len(mapper._stored_commands) == 0 for qb0_id, qb1_id in itertools.permutations(range(8), 2): - if ((qb0_id, qb1_id) not in neighbours and - (qb1_id, qb0_id) not in neighbours): + if (qb0_id, qb1_id) not in neighbours and (qb1_id, qb0_id) not in neighbours: qb0 = WeakQubitRef(engine=None, idx=qb0_id) qb1 = WeakQubitRef(engine=None, idx=qb1_id) cmd = Command(None, X, qubits=([qb0],), controls=[qb1]) @@ -204,14 +245,12 @@ def test_send_possible_commands_allocate(different_backend_ids): map_to_backend_ids = {0: 21, 1: 32, 2: 3, 3: 4, 4: 5, 5: 6} else: map_to_backend_ids = None - mapper = two_d.GridMapper(num_rows=3, num_columns=2, - mapped_ids_to_backend_ids=map_to_backend_ids) + mapper = two_d.GridMapper(num_rows=3, num_columns=2, mapped_ids_to_backend_ids=map_to_backend_ids) backend = DummyEngine(save_commands=True) backend.is_last_engine = True mapper.next_engine = backend qb0 = WeakQubitRef(engine=None, idx=0) - cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], - tags=[]) + cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] mapper._currently_allocated_ids = set([10]) # not in mapping: @@ -226,8 +265,13 @@ def test_send_possible_commands_allocate(different_backend_ids): assert len(mapper._stored_commands) == 0 # Only self._run() sends Allocate gates mapped0 = WeakQubitRef(engine=None, idx=3) - received_cmd = Command(engine=mapper, gate=Allocate, qubits=([mapped0],), - controls=[], tags=[LogicalQubitIDTag(0)]) + received_cmd = Command( + engine=mapper, + gate=Allocate, + qubits=([mapped0],), + controls=[], + tags=[LogicalQubitIDTag(0)], + ) assert backend.received_commands[0] == received_cmd assert mapper._currently_allocated_ids == set([10, 0]) @@ -238,14 +282,12 @@ def test_send_possible_commands_deallocate(different_backend_ids): map_to_backend_ids = {0: 21, 1: 32, 2: 3, 3: 4, 4: 5, 5: 6} else: map_to_backend_ids = None - mapper = two_d.GridMapper(num_rows=3, num_columns=2, - mapped_ids_to_backend_ids=map_to_backend_ids) + mapper = two_d.GridMapper(num_rows=3, num_columns=2, mapped_ids_to_backend_ids=map_to_backend_ids) backend = DummyEngine(save_commands=True) backend.is_last_engine = True mapper.next_engine = backend qb0 = WeakQubitRef(engine=None, idx=0) - cmd0 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], - tags=[]) + cmd0 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], tags=[]) mapper._stored_commands = [cmd0] mapper.current_mapping = dict() mapper._currently_allocated_ids = set([10]) @@ -269,19 +311,15 @@ def test_send_possible_commands_keep_remaining_gates(different_backend_ids): map_to_backend_ids = {0: 21, 1: 32, 2: 3, 3: 0, 4: 5, 5: 6} else: map_to_backend_ids = None - mapper = two_d.GridMapper(num_rows=3, num_columns=2, - mapped_ids_to_backend_ids=map_to_backend_ids) + mapper = two_d.GridMapper(num_rows=3, num_columns=2, mapped_ids_to_backend_ids=map_to_backend_ids) backend = DummyEngine(save_commands=True) backend.is_last_engine = True mapper.next_engine = backend qb0 = WeakQubitRef(engine=None, idx=0) qb1 = WeakQubitRef(engine=None, idx=1) - cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], - tags=[]) - cmd1 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], - tags=[]) - cmd2 = Command(engine=None, gate=Allocate, qubits=([qb1],), controls=[], - tags=[]) + cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], tags=[]) + cmd1 = Command(engine=None, gate=Deallocate, qubits=([qb0],), controls=[], tags=[]) + cmd2 = Command(engine=None, gate=Allocate, qubits=([qb1],), controls=[], tags=[]) mapper._stored_commands = [cmd0, cmd1, cmd2] mapper.current_mapping = {0: 0} @@ -295,15 +333,13 @@ def test_send_possible_commands_one_inactive_qubit(different_backend_ids): map_to_backend_ids = {0: 21, 1: 32, 2: 3, 3: 0, 4: 5, 5: 6} else: map_to_backend_ids = None - mapper = two_d.GridMapper(num_rows=3, num_columns=2, - mapped_ids_to_backend_ids=map_to_backend_ids) + mapper = two_d.GridMapper(num_rows=3, num_columns=2, mapped_ids_to_backend_ids=map_to_backend_ids) backend = DummyEngine(save_commands=True) backend.is_last_engine = True mapper.next_engine = backend qb0 = WeakQubitRef(engine=None, idx=0) qb1 = WeakQubitRef(engine=None, idx=1) - cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], - tags=[]) + cmd0 = Command(engine=None, gate=Allocate, qubits=([qb0],), controls=[], tags=[]) cmd1 = Command(engine=None, gate=X, qubits=([qb0],), controls=[qb1]) mapper._stored_commands = [cmd0, cmd1] mapper.current_mapping = {0: 0} @@ -329,7 +365,8 @@ def choose_last_permutation(swaps): num_columns=2, mapped_ids_to_backend_ids=map_to_backend_ids, optimization_function=choose_last_permutation, - num_optimization_steps=num_optimization_steps) + num_optimization_steps=num_optimization_steps, + ) backend = DummyEngine(save_commands=True) backend.is_last_engine = True mapper.next_engine = backend @@ -356,15 +393,19 @@ def choose_last_permutation(swaps): assert len(backend.received_commands) == 10 assert mapper._currently_allocated_ids == set([0, 2, 3]) if different_backend_ids: - assert (mapper.current_mapping == {0: 21, 2: 3, 3: 0} or - mapper.current_mapping == {0: 32, 2: 0, 3: 21} or - mapper.current_mapping == {0: 3, 2: 21, 3: 32} or - mapper.current_mapping == {0: 0, 2: 32, 3: 3}) + assert ( + mapper.current_mapping == {0: 21, 2: 3, 3: 0} + or mapper.current_mapping == {0: 32, 2: 0, 3: 21} + or mapper.current_mapping == {0: 3, 2: 21, 3: 32} + or mapper.current_mapping == {0: 0, 2: 32, 3: 3} + ) else: - assert (mapper.current_mapping == {0: 0, 2: 2, 3: 3} or - mapper.current_mapping == {0: 1, 2: 3, 3: 0} or - mapper.current_mapping == {0: 2, 2: 0, 3: 1} or - mapper.current_mapping == {0: 3, 2: 1, 3: 2}) + assert ( + mapper.current_mapping == {0: 0, 2: 2, 3: 3} + or mapper.current_mapping == {0: 1, 2: 3, 3: 0} + or mapper.current_mapping == {0: 2, 2: 0, 3: 1} + or mapper.current_mapping == {0: 3, 2: 1, 3: 2} + ) cmd9 = Command(None, X, qubits=([qb0],), controls=[qb3]) mapper.storage = 1 mapper.receive([cmd9]) @@ -419,8 +460,7 @@ def test_correct_stats(): cmd8 = Command(None, X, qubits=([qb1],), controls=[qb2]) qb_flush = WeakQubitRef(engine=None, idx=-1) cmd_flush = Command(engine=None, gate=FlushGate(), qubits=([qb_flush],)) - mapper.receive([cmd0, cmd1, cmd2, cmd3, cmd4, cmd5, cmd6, cmd7, cmd8, - cmd_flush]) + mapper.receive([cmd0, cmd1, cmd2, cmd3, cmd4, cmd5, cmd6, cmd7, cmd8, cmd_flush]) assert mapper.num_mappings == 2 @@ -430,7 +470,8 @@ def test_send_possible_cmds_before_new_mapping(): backend.is_last_engine = True mapper.next_engine = backend - def dont_call_mapping(): raise Exception + def dont_call_mapping(): + raise Exception mapper._return_new_mapping = dont_call_mapping mapper.current_mapping = {0: 1} diff --git a/projectq/libs/__init__.py b/projectq/libs/__init__.py index ee1451dcd..16fc4afdf 100755 --- a/projectq/libs/__init__.py +++ b/projectq/libs/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/hist/__init__.py b/projectq/libs/hist/__init__.py index 088766263..b4e9085db 100644 --- a/projectq/libs/hist/__init__.py +++ b/projectq/libs/hist/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ contains a function to plot measurement outcome probabilities as a histogram for the simulator diff --git a/projectq/libs/hist/_histogram.py b/projectq/libs/hist/_histogram.py index 77b0d2c20..849bddbd4 100644 --- a/projectq/libs/hist/_histogram.py +++ b/projectq/libs/hist/_histogram.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,8 +46,7 @@ def histogram(backend, qureg): qubit_list.append(q) if len(qubit_list) > 5: - print('Warning: For {0} qubits there are 2^{0} different outcomes'. - format(len(qubit_list))) + print('Warning: For {0} qubits there are 2^{0} different outcomes'.format(len(qubit_list))) print("The resulting histogram may look bad and/or take too long.") print("Consider calling histogram() with a sublist of the qubits.") @@ -54,7 +54,7 @@ def histogram(backend, qureg): probabilities = backend.get_probabilities(qureg) elif isinstance(backend, Simulator): outcome = [0] * len(qubit_list) - n_outcomes = (1 << len(qubit_list)) + n_outcomes = 1 << len(qubit_list) probabilities = {} for i in range(n_outcomes): for pos in range(len(qubit_list)): @@ -62,15 +62,12 @@ def histogram(backend, qureg): outcome[pos] = 1 else: outcome[pos] = 0 - probabilities[''.join([str(bit) for bit in outcome - ])] = backend.get_probability( - outcome, qubit_list) + probabilities[''.join([str(bit) for bit in outcome])] = backend.get_probability(outcome, qubit_list) else: raise RuntimeError('Unable to retrieve probabilities from backend') # Empirical figure size for up to 5 qubits - fig, axes = plt.subplots(figsize=(min(21.2, 2 - + 0.6 * (1 << len(qubit_list))), 7)) + fig, axes = plt.subplots(figsize=(min(21.2, 2 + 0.6 * (1 << len(qubit_list))), 7)) names = list(probabilities.keys()) values = list(probabilities.values()) axes.bar(names, values) diff --git a/projectq/libs/hist/_histogram_test.py b/projectq/libs/hist/_histogram_test.py index 1f38573b0..c6cb78a95 100644 --- a/projectq/libs/hist/_histogram_test.py +++ b/projectq/libs/hist/_histogram_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,7 +15,7 @@ import pytest import matplotlib -import matplotlib.pyplot as plt +import matplotlib.pyplot as plt # noqa: F401 from projectq import MainEngine from projectq.ops import H, C, X, Measure, All, AllocateQubitGate, FlushGate @@ -60,6 +61,10 @@ def receive(self, command_list): assert prob['000'] == 0.5 assert prob['111'] == 0.5 + # NB: avoid throwing exceptions when destroying the MainEngine + eng.next_engine = DummyEngine() + eng.next_engine.is_last_engine = True + def test_qubit(matplotlib_setup): sim = Simulator() @@ -97,10 +102,13 @@ def test_qureg(matplotlib_setup): All(Measure) | qureg eng.flush() _, _, prob = histogram(sim, qureg) - assert prob["000"] == pytest.approx(1) or prob["001"] == pytest.approx(1) \ - or prob["110"] == pytest.approx(1) or prob["111"] == pytest.approx(1) - assert prob["000"] + prob["001"] + prob["110"] + prob[ - "111"] == pytest.approx(1) + assert ( + prob["000"] == pytest.approx(1) + or prob["001"] == pytest.approx(1) + or prob["110"] == pytest.approx(1) + or prob["111"] == pytest.approx(1) + ) + assert prob["000"] + prob["001"] + prob["110"] + prob["111"] == pytest.approx(1) def test_combination(matplotlib_setup): @@ -117,8 +125,9 @@ def test_combination(matplotlib_setup): Measure | qureg[0] eng.flush() _, _, prob = histogram(sim, [qureg, qubit]) - assert (prob["000"] == pytest.approx(0.5) and prob["001"] == pytest.approx(0.5)) \ - or (prob["110"] == pytest.approx(0.5) and prob["111"] == pytest.approx(0.5)) + assert (prob["000"] == pytest.approx(0.5) and prob["001"] == pytest.approx(0.5)) or ( + prob["110"] == pytest.approx(0.5) and prob["111"] == pytest.approx(0.5) + ) assert prob["100"] == pytest.approx(0) Measure | qubit diff --git a/projectq/libs/math/__init__.py b/projectq/libs/math/__init__.py index 8a0543be2..f950ab3cf 100755 --- a/projectq/libs/math/__init__.py +++ b/projectq/libs/math/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,15 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ._gates import (AddConstant, - SubConstant, - AddConstantModN, - SubConstantModN, - MultiplyByConstantModN, - AddQuantum, - SubtractQuantum, - ComparatorQuantum, - DivideQuantum, - MultiplyQuantum) +from ._gates import ( + AddConstant, + SubConstant, + AddConstantModN, + SubConstantModN, + MultiplyByConstantModN, + AddQuantum, + SubtractQuantum, + ComparatorQuantum, + DivideQuantum, + MultiplyQuantum, +) from ._default_rules import all_defined_decomposition_rules diff --git a/projectq/libs/math/_constantmath.py b/projectq/libs/math/_constantmath.py index 2642c0ad3..f8f693d85 100755 --- a/projectq/libs/math/_constantmath.py +++ b/projectq/libs/math/_constantmath.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,6 +14,7 @@ # limitations under the License. import math + try: from math import gcd except ImportError: # pragma: no cover @@ -38,7 +40,7 @@ def add_constant(eng, c, quint): for i in range(len(quint)): for j in range(i, -1, -1): - if ((c >> j) & 1): + if (c >> j) & 1: R(math.pi / (1 << (i - j))) | quint[i] Uncompute(eng) @@ -51,7 +53,7 @@ def add_constant_modN(eng, c, N, quint): using Draper addition and the construction from https://arxiv.org/abs/quant-ph/0205095. """ - assert (c < N and c >= 0) + assert c < N and c >= 0 AddConstant(c) | quint @@ -84,8 +86,8 @@ def mul_by_constant_modN(eng, c, N, quint_in): (only works if a and N are relative primes, otherwise the modular inverse does not exist). """ - assert (c < N and c >= 0) - assert (gcd(c, N) == 1) + assert c < N and c >= 0 + assert gcd(c, N) == 1 n = len(quint_in) quint_out = eng.allocate_qureg(n + 1) diff --git a/projectq/libs/math/_constantmath_test.py b/projectq/libs/math/_constantmath_test.py index c9abeda15..4de8e430d 100755 --- a/projectq/libs/math/_constantmath_test.py +++ b/projectq/libs/math/_constantmath_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,16 +17,13 @@ import pytest from projectq import MainEngine -from projectq.cengines import (InstructionFilter, AutoReplacer, - DecompositionRuleSet) +from projectq.cengines import InstructionFilter, AutoReplacer, DecompositionRuleSet from projectq.backends import Simulator -from projectq.ops import (All, BasicMathGate, ClassicalInstructionGate, - Measure, X) +from projectq.ops import All, BasicMathGate, ClassicalInstructionGate, Measure, X import projectq.libs.math from projectq.setups.decompositions import qft2crandhadamard, swap2cnot -from projectq.libs.math import (AddConstant, AddConstantModN, - MultiplyByConstantModN) +from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN def init(engine, quint, value): @@ -47,15 +45,13 @@ def no_math_emulation(eng, cmd): @pytest.fixture def eng(): - return MainEngine(backend=Simulator(), - engine_list=[ - AutoReplacer(rule_set), - InstructionFilter(no_math_emulation) - ]) + return MainEngine( + backend=Simulator(), + engine_list=[AutoReplacer(rule_set), InstructionFilter(no_math_emulation)], + ) -rule_set = DecompositionRuleSet( - modules=[projectq.libs.math, qft2crandhadamard, swap2cnot]) +rule_set = DecompositionRuleSet(modules=[projectq.libs.math, qft2crandhadamard, swap2cnot]) def test_adder(eng): @@ -64,14 +60,14 @@ def test_adder(eng): AddConstant(3) | qureg - assert 1. == pytest.approx(abs(eng.backend.cheat()[1][7])) + assert 1.0 == pytest.approx(abs(eng.backend.cheat()[1][7])) init(eng, qureg, 7) # reset init(eng, qureg, 2) # check for overflow -> should be 15+2 = 1 (mod 16) AddConstant(15) | qureg - assert 1. == pytest.approx(abs(eng.backend.cheat()[1][1])) + assert 1.0 == pytest.approx(abs(eng.backend.cheat()[1][1])) All(Measure) | qureg @@ -82,13 +78,13 @@ def test_modadder(eng): AddConstantModN(3, 6) | qureg - assert 1. == pytest.approx(abs(eng.backend.cheat()[1][1])) + assert 1.0 == pytest.approx(abs(eng.backend.cheat()[1][1])) init(eng, qureg, 1) # reset init(eng, qureg, 7) AddConstantModN(10, 13) | qureg - assert 1. == pytest.approx(abs(eng.backend.cheat()[1][4])) + assert 1.0 == pytest.approx(abs(eng.backend.cheat()[1][4])) All(Measure) | qureg @@ -99,12 +95,12 @@ def test_modmultiplier(eng): MultiplyByConstantModN(3, 7) | qureg - assert 1. == pytest.approx(abs(eng.backend.cheat()[1][5])) + assert 1.0 == pytest.approx(abs(eng.backend.cheat()[1][5])) init(eng, qureg, 5) # reset init(eng, qureg, 7) MultiplyByConstantModN(4, 13) | qureg - assert 1. == pytest.approx(abs(eng.backend.cheat()[1][2])) + assert 1.0 == pytest.approx(abs(eng.backend.cheat()[1][2])) All(Measure) | qureg diff --git a/projectq/libs/math/_default_rules.py b/projectq/libs/math/_default_rules.py index b69f35fe0..89e3548e5 100755 --- a/projectq/libs/math/_default_rules.py +++ b/projectq/libs/math/_default_rules.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,12 +20,22 @@ from projectq.meta import Control from projectq.cengines import DecompositionRule -from ._gates import (AddConstant, AddConstantModN, MultiplyByConstantModN, - AddQuantum, SubtractQuantum, ComparatorQuantum, - DivideQuantum, MultiplyQuantum) +from ._gates import ( + AddConstant, + AddConstantModN, + MultiplyByConstantModN, + AddQuantum, + SubtractQuantum, + ComparatorQuantum, + DivideQuantum, + MultiplyQuantum, +) -from ._gates import (_InverseAddQuantumGate, _InverseDivideQuantumGate, - _InverseMultiplyQuantumGate) +from ._gates import ( + _InverseAddQuantumGate, + _InverseDivideQuantumGate, + _InverseMultiplyQuantumGate, +) from ._constantmath import ( add_constant, @@ -33,10 +44,17 @@ ) from ._quantummath import ( - add_quantum, subtract_quantum, inverse_add_quantum_carry, comparator, - quantum_conditional_add, quantum_division, inverse_quantum_division, - quantum_conditional_add_carry, quantum_multiplication, - inverse_quantum_multiplication) + add_quantum, + subtract_quantum, + inverse_add_quantum_carry, + comparator, + quantum_conditional_add, + quantum_division, + inverse_quantum_division, + quantum_conditional_add_carry, + quantum_multiplication, + inverse_quantum_multiplication, +) def _replace_addconstant(cmd): @@ -84,12 +102,10 @@ def _replace_addquantum(cmd): if len(cmd.qubits) == 3: c = cmd.qubits[2] with Control(eng, cmd.control_qubits): - quantum_conditional_add_carry(eng, quint_a, quint_b, - cmd.control_qubits, c) + quantum_conditional_add_carry(eng, quint_a, quint_b, cmd.control_qubits, c) else: with Control(eng, cmd.control_qubits): - quantum_conditional_add(eng, quint_a, quint_b, - cmd.control_qubits) + quantum_conditional_add(eng, quint_a, quint_b, cmd.control_qubits) def _replace_inverse_add_quantum(cmd): @@ -165,10 +181,7 @@ def _replace_inversequantummultiplication(cmd): DecompositionRule(SubtractQuantum.__class__, _replace_inverse_add_quantum), DecompositionRule(ComparatorQuantum.__class__, _replace_comparator), DecompositionRule(DivideQuantum.__class__, _replace_quantumdivision), - DecompositionRule(_InverseDivideQuantumGate, - _replace_inversequantumdivision), - DecompositionRule(MultiplyQuantum.__class__, - _replace_quantummultiplication), - DecompositionRule(_InverseMultiplyQuantumGate, - _replace_inversequantummultiplication), + DecompositionRule(_InverseDivideQuantumGate, _replace_inversequantumdivision), + DecompositionRule(MultiplyQuantum.__class__, _replace_quantummultiplication), + DecompositionRule(_InverseMultiplyQuantumGate, _replace_inversequantummultiplication), ] diff --git a/projectq/libs/math/_gates.py b/projectq/libs/math/_gates.py index d63ae949d..794281432 100755 --- a/projectq/libs/math/_gates.py +++ b/projectq/libs/math/_gates.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,6 +32,7 @@ class AddConstant(BasicMathGate): Important: if you run with conditional and carry, carry needs to be a quantum register for the compiler/decomposition to work. """ + def __init__(self, a): """ Initializes the gate to the number to add. @@ -41,7 +43,7 @@ def __init__(self, a): It also initializes its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ - BasicMathGate.__init__(self, lambda x: ((x + a), )) + BasicMathGate.__init__(self, lambda x: ((x + a),)) self.a = a def get_inverse(self): @@ -103,6 +105,7 @@ class AddConstantModN(BasicMathGate): * c >= 0 * The value stored in the quantum register must be lower than N """ + def __init__(self, a, N): """ Initializes the gate to the number to add modulo N. @@ -114,7 +117,7 @@ def __init__(self, a, N): It also initializes its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ - BasicMathGate.__init__(self, lambda x: ((x + a) % N, )) + BasicMathGate.__init__(self, lambda x: ((x + a) % N,)) self.a = a self.N = N @@ -129,8 +132,7 @@ def get_inverse(self): return SubConstantModN(self.a, self.N) def __eq__(self, other): - return (isinstance(other, AddConstantModN) and self.a == other.a - and self.N == other.N) + return isinstance(other, AddConstantModN) and self.a == other.a and self.N == other.N def __hash__(self): return hash(str(self)) @@ -192,6 +194,7 @@ class MultiplyByConstantModN(BasicMathGate): * gcd(c, N) == 1 * The value stored in the quantum register must be lower than N """ + def __init__(self, a, N): """ Initializes the gate to the number to multiply with modulo N. @@ -204,7 +207,7 @@ def __init__(self, a, N): It also initializes its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ - BasicMathGate.__init__(self, lambda x: ((a * x) % N, )) + BasicMathGate.__init__(self, lambda x: ((a * x) % N,)) self.a = a self.N = N @@ -212,8 +215,7 @@ def __str__(self): return "MultiplyByConstantModN({}, {})".format(self.a, self.N) def __eq__(self, other): - return (isinstance(other, MultiplyByConstantModN) and self.a == other.a - and self.N == other.N) + return isinstance(other, MultiplyByConstantModN) and self.a == other.a and self.N == other.N def __hash__(self): return hash(str(self)) @@ -239,6 +241,7 @@ class AddQuantumGate(BasicMathGate): AddQuantum | (qunum_a, qunum_b, carry) # qunum_a remains 4, qunum_b is now 12 and carry_bit is 0 """ + def __init__(self): BasicMathGate.__init__(self, None) @@ -246,7 +249,7 @@ def __str__(self): return "AddQuantum" def __eq__(self, other): - return (isinstance(other, AddQuantumGate)) + return isinstance(other, AddQuantumGate) def __hash__(self): return hash(str(self)) @@ -260,12 +263,12 @@ def get_math_function(self, qubits): def math_fun(a): a[1] = a[0] + a[1] if len(bin(a[1])[2:]) > n: - a[1] = a[1] % (2**n) + a[1] = a[1] % (2 ** n) if len(a) == 3: # Flip the last bit of the carry register a[2] ^= 1 - return (a) + return a return math_fun @@ -285,6 +288,7 @@ class _InverseAddQuantumGate(BasicMathGate): Internal gate glass to support emulation for inverse addition. """ + def __init__(self): BasicMathGate.__init__(self, None) @@ -292,15 +296,13 @@ def __str__(self): return "_InverseAddQuantum" def get_math_function(self, qubits): - n = len(qubits[1]) - def math_fun(a): if len(a) == 3: # Flip the last bit of the carry register a[2] ^= 1 a[1] -= a[0] - return (a) + return a return math_fun @@ -321,11 +323,13 @@ class SubtractQuantumGate(BasicMathGate): # qunum_a remains 4, qunum_b is now 4 """ + def __init__(self): """ Initializes the gate to its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ + def subtract(a, b): return (a, b - a) @@ -335,7 +339,7 @@ def __str__(self): return "SubtractQuantum" def __eq__(self, other): - return (isinstance(other, SubtractQuantumGate)) + return isinstance(other, SubtractQuantumGate) def __hash__(self): return hash(str(self)) @@ -372,11 +376,13 @@ class ComparatorQuantumGate(BasicMathGate): compare bit is now 1 """ + def __init__(self): """ Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ + def compare(a, b, c): if b < a: if c == 0: @@ -391,7 +397,7 @@ def __str__(self): return "Comparator" def __eq__(self, other): - return (isinstance(other, ComparatorQuantumGate)) + return isinstance(other, ComparatorQuantumGate) def __hash__(self): return hash(str(self)) @@ -435,11 +441,13 @@ class DivideQuantumGate(BasicMathGate): |dividend>|remainder>|divisor> -> |remainder>|quotient>|divisor> """ + def __init__(self): """ Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ + def division(dividend, remainder, divisor): if divisor == 0 or divisor > dividend: return (remainder, dividend, divisor) @@ -457,7 +465,7 @@ def __str__(self): return "DivideQuantum" def __eq__(self, other): - return (isinstance(other, DivideQuantumGate)) + return isinstance(other, DivideQuantumGate) def __hash__(self): return hash(str(self)) @@ -474,6 +482,7 @@ class _InverseDivideQuantumGate(BasicMathGate): Internal gate glass to support emulation for inverse division. """ + def __init__(self): def inverse_division(remainder, quotient, divisor): if divisor == 0: @@ -488,6 +497,7 @@ def inverse_division(remainder, quotient, divisor): def __str__(self): return "_InverseDivideQuantum" + class MultiplyQuantumGate(BasicMathGate): """ Multiplies two quantum numbers represented by a quantum registers. @@ -498,6 +508,7 @@ class MultiplyQuantumGate(BasicMathGate): Example: .. code-block:: python + qunum_a = eng.allocate_qureg(4) qunum_b = eng.allocate_qureg(4) qunum_c = eng.allocate_qureg(9) @@ -506,11 +517,13 @@ class MultiplyQuantumGate(BasicMathGate): MultiplyQuantum() | (qunum_a, qunum_b, qunum_c) # qunum_a remains 4 and qunum_b remains 8, qunum_c is now equal to 32 """ + def __init__(self): """ Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated efficiently. """ + def multiply(a, b, c): return (a, b, c + a * b) @@ -520,7 +533,7 @@ def __str__(self): return "MultiplyQuantum" def __eq__(self, other): - return (isinstance(other, MultiplyQuantumGate)) + return isinstance(other, MultiplyQuantumGate) def __hash__(self): return hash(str(self)) @@ -540,6 +553,7 @@ class _InverseMultiplyQuantumGate(BasicMathGate): Internal gate glass to support emulation for inverse multiplication. """ + def __init__(self): def inverse_multiplication(a, b, c): return (a, b, c - a * b) diff --git a/projectq/libs/math/_gates_math_test.py b/projectq/libs/math/_gates_math_test.py index 2717c5927..35b265c8e 100644 --- a/projectq/libs/math/_gates_math_test.py +++ b/projectq/libs/math/_gates_math_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,16 +16,26 @@ import pytest -from projectq.cengines import (MainEngine, TagRemover, AutoReplacer, - InstructionFilter, DecompositionRuleSet) +from projectq.cengines import ( + MainEngine, + TagRemover, + AutoReplacer, + InstructionFilter, + DecompositionRuleSet, +) from projectq.meta import Control, Compute, Uncompute -from projectq.ops import (All, Measure, X, BasicMathGate, - ClassicalInstructionGate) +from projectq.ops import All, Measure, X, BasicMathGate, ClassicalInstructionGate import projectq.setups.decompositions import projectq.libs.math -from . import (AddConstant, AddQuantum, SubtractQuantum, ComparatorQuantum, - DivideQuantum, MultiplyQuantum) +from . import ( + AddConstant, + AddQuantum, + SubtractQuantum, + ComparatorQuantum, + DivideQuantum, + MultiplyQuantum, +) from projectq.backends import CommandPrinter @@ -32,7 +43,7 @@ def print_all_probabilities(eng, qureg): i = 0 y = len(qureg) - while i < (2**y): + while i < (2 ** y): qubit_list = [int(x) for x in list(('{0:0b}'.format(i)).zfill(y))] qubit_list = qubit_list[::-1] prob = eng.backend.get_probability(qubit_list, qureg) @@ -46,13 +57,15 @@ def _eng_emulation(): # Only decomposing native ProjectQ gates # -> using emulation for gates in projectq.libs.math rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - eng = MainEngine(engine_list=[ - TagRemover(), - AutoReplacer(rule_set), - TagRemover(), - CommandPrinter(), - ], - verbose=True) + eng = MainEngine( + engine_list=[ + TagRemover(), + AutoReplacer(rule_set), + TagRemover(), + CommandPrinter(), + ], + verbose=True, + ) return eng @@ -67,16 +80,16 @@ def no_math_emulation(eng, cmd): except AttributeError: return False - rule_set = DecompositionRuleSet(modules=[ - projectq.libs.math, projectq.setups.decompositions.qft2crandhadamard - ]) - eng = MainEngine(engine_list=[ - TagRemover(), - AutoReplacer(rule_set), - InstructionFilter(no_math_emulation), - TagRemover(), - CommandPrinter() - ]) + rule_set = DecompositionRuleSet(modules=[projectq.libs.math, projectq.setups.decompositions.qft2crandhadamard]) + eng = MainEngine( + engine_list=[ + TagRemover(), + AutoReplacer(rule_set), + InstructionFilter(no_math_emulation), + TagRemover(), + CommandPrinter(), + ] + ) return eng @@ -98,8 +111,7 @@ def test_constant_addition(eng): eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) def test_addition(eng): @@ -112,11 +124,9 @@ def test_addition(eng): eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 1, 1, 0], qunum_b)) - assert 1. == pytest.approx(eng.backend.get_probability([0], carry_bit)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 1, 1, 0], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0], carry_bit)) def test_inverse_addition(eng): @@ -130,10 +140,8 @@ def test_inverse_addition(eng): eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) def test_inverse_addition_with_control(eng): @@ -151,10 +159,8 @@ def test_inverse_addition_with_control(eng): eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 1, 1, 1], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 1, 1, 1], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 1, 1, 1], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 1, 1, 1], qunum_b)) def test_addition_with_control(eng): @@ -169,10 +175,8 @@ def test_addition_with_control(eng): eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 0, 0, 0], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 0, 0, 1], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 0, 0, 0], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 0, 0, 1], qunum_b)) def test_addition_with_control_carry(eng): @@ -192,12 +196,10 @@ def test_addition_with_control_carry(eng): eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 0, 0], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 0, 0], qunum_b)) - assert 1. == pytest.approx(eng.backend.get_probability([1], control_bit)) - assert 1. == pytest.approx(eng.backend.get_probability([1, 0], qunum_c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 0, 0], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 0, 0], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1], control_bit)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0], qunum_c)) All(Measure) | qunum_a All(Measure) | qunum_b @@ -220,12 +222,10 @@ def test_inverse_addition_with_control_carry(eng): eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 0, 0], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 1, 1], qunum_b)) - assert 1. == pytest.approx(eng.backend.get_probability([1], control_bit)) - assert 1. == pytest.approx(eng.backend.get_probability([0, 0], qunum_c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 0, 0], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 1, 1], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1], control_bit)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0], qunum_c)) All(Measure) | qunum_a All(Measure) | qunum_b @@ -244,10 +244,8 @@ def test_subtraction(eng): eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 1, 0, 0], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 1, 0, 0], qunum_b)) def test_inverse_subtraction(eng): @@ -263,10 +261,8 @@ def test_inverse_subtraction(eng): eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) def test_comparator(eng): @@ -281,11 +277,9 @@ def test_comparator(eng): eng.flush() print_all_probabilities(eng, qunum_a) print_all_probabilities(eng, qunum_b) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 0, 1], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) - assert 1. == pytest.approx(eng.backend.get_probability([1], compare_bit)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 0, 1], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1], compare_bit)) def test_division(eng): @@ -299,12 +293,9 @@ def test_division(eng): DivideQuantum | (qunum_a, qunum_b, qunum_c) eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 0, 0, 0], qunum_a)) # remainder - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 0, 0, 0], qunum_b)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 1, 0, 0], qunum_c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 0, 0, 0], qunum_a)) # remainder + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 0, 0, 0], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 1, 0, 0], qunum_c)) def test_inverse_division(eng): @@ -320,12 +311,9 @@ def test_inverse_division(eng): Uncompute(eng) eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 0, 1, 0], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 0, 0], qunum_b)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 1, 0, 0], qunum_c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 0, 1, 0], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 0, 0], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 1, 0, 0], qunum_c)) def test_multiplication(eng): @@ -339,12 +327,9 @@ def test_multiplication(eng): eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 0, 0, 1, 0, 0, 0], qunum_c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 0, 0, 1, 0, 0, 0], qunum_c)) def test_inverse_multiplication(eng): @@ -359,9 +344,6 @@ def test_inverse_multiplication(eng): eng.flush() - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 0, 0, 0, 0, 0, 0], qunum_c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 1, 0, 0], qunum_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 1, 0], qunum_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 0, 0, 0, 0, 0, 0], qunum_c)) diff --git a/projectq/libs/math/_gates_test.py b/projectq/libs/math/_gates_test.py index 116336204..2bc50a816 100755 --- a/projectq/libs/math/_gates_test.py +++ b/projectq/libs/math/_gates_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,14 +14,26 @@ # limitations under the License. """Tests for projectq.libs.math._gates.py.""" -from projectq.libs.math import (AddConstant, AddConstantModN, - MultiplyByConstantModN, SubConstant, - SubConstantModN, AddQuantum, SubtractQuantum, - ComparatorQuantum, DivideQuantum, - MultiplyQuantum) - -from ._gates import (AddQuantumGate, SubtractQuantumGate, MultiplyQuantumGate, - DivideQuantumGate, ComparatorQuantumGate) +from projectq.libs.math import ( + AddConstant, + AddConstantModN, + MultiplyByConstantModN, + SubConstant, + SubConstantModN, + AddQuantum, + SubtractQuantum, + ComparatorQuantum, + DivideQuantum, + MultiplyQuantum, +) + +from ._gates import ( + AddQuantumGate, + SubtractQuantumGate, + MultiplyQuantumGate, + DivideQuantumGate, + ComparatorQuantumGate, +) def test_addconstant(): @@ -88,8 +101,7 @@ def test_hash_function_implemented(): assert hash(SubConstant(-3)) == hash(str(AddConstant(3))) assert hash(AddConstantModN(7, 4)) == hash(str(AddConstantModN(7, 4))) assert hash(SubConstantModN(7, 4)) == hash(str(AddConstantModN(-3, 4))) - assert hash(MultiplyByConstantModN(3, 5)) == hash( - str(MultiplyByConstantModN(3, 5))) + assert hash(MultiplyByConstantModN(3, 5)) == hash(str(MultiplyByConstantModN(3, 5))) assert hash(AddQuantum) == hash(str(AddQuantum)) assert hash(SubtractQuantum) == hash(str(SubtractQuantum)) assert hash(ComparatorQuantum) == hash(str(ComparatorQuantum)) diff --git a/projectq/libs/math/_quantummath.py b/projectq/libs/math/_quantummath.py index 5c4d5ed5b..8e9acfcc2 100644 --- a/projectq/libs/math/_quantummath.py +++ b/projectq/libs/math/_quantummath.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,7 +15,7 @@ from projectq.ops import All, X, CNOT from projectq.meta import Control -from ._gates import (AddQuantum, SubtractQuantum) +from ._gates import AddQuantum, SubtractQuantum def add_quantum(eng, quint_a, quint_b, carry=None): @@ -64,7 +65,7 @@ def add_quantum(eng, quint_a, quint_b, carry=None): with Control(eng, [quint_a[n - 2], quint_b[n - 2]]): X | carry - for l in range(n - 2, 0, -1): + for l in range(n - 2, 0, -1): # noqa: E741 CNOT | (quint_a[l], quint_b[l]) with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): X | quint_a[l] @@ -121,7 +122,7 @@ def subtract_quantum(eng, quint_a, quint_b): with Control(eng, [quint_a[k], quint_b[k]]): X | (quint_a[k + 1]) - for l in range(n - 2, 0, -1): + for l in range(n - 2, 0, -1): # noqa: E741 CNOT | (quint_a[l], quint_b[l]) with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): X | quint_a[l] @@ -147,7 +148,7 @@ def inverse_add_quantum_carry(eng, quint_a, quint_b): # pylint: disable = pointless-statement, expression-not-assigned # pylint: disable = unused-argument - assert (len(quint_a) == len(quint_b[0])) + assert len(quint_a) == len(quint_b[0]) All(X) | quint_b[0] X | quint_b[1][0] @@ -266,7 +267,7 @@ def quantum_conditional_add(eng, quint_a, quint_b, conditional): with Control(eng, [quint_a[n - 2], conditional[0]]): X | quint_b[n - 2] - for l in range(n - 2, 0, -1): + for l in range(n - 2, 0, -1): # noqa: E741 with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): X | quint_a[l] with Control(eng, [quint_a[l - 1], conditional[0]]): @@ -349,7 +350,7 @@ def inverse_quantum_division(eng, remainder, quotient, divisor): """ # pylint: disable = pointless-statement, expression-not-assigned - assert (len(remainder) == len(quotient) == len(divisor)) + assert len(remainder) == len(quotient) == len(divisor) j = 0 n = len(quotient) @@ -428,7 +429,7 @@ def quantum_conditional_add_carry(eng, quint_a, quint_b, ctrl, z): with Control(eng, [quint_b[n - 1], quint_a[n - 1]]): X | z[1] - for l in range(n - 1, 0, -1): + for l in range(n - 1, 0, -1): # noqa: E741 with Control(eng, [ctrl[0], quint_a[l]]): X | quint_b[l] with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): @@ -471,22 +472,28 @@ def quantum_multiplication(eng, quint_a, quint_b, product): """ # pylint: disable = pointless-statement, expression-not-assigned - assert (len(quint_a) == len(quint_b)) + assert len(quint_a) == len(quint_b) n = len(quint_a) - assert (len(product) == ((2 * n) + 1)) + assert len(product) == ((2 * n) + 1) for i in range(0, n): with Control(eng, [quint_a[i], quint_b[0]]): X | product[i] with Control(eng, quint_b[1]): - AddQuantum | (quint_a[0:(n - 1)], product[1:n], - [product[n + 1], product[n + 2]]) + AddQuantum | ( + quint_a[0 : (n - 1)], # noqa: E203 + product[1:n], + [product[n + 1], product[n + 2]], + ) for j in range(2, n): with Control(eng, quint_b[j]): - AddQuantum | (quint_a[0:(n - 1)], product[(0 + j):(n - 1 + j)], - [product[n + j], product[n + j + 1]]) + AddQuantum | ( + quint_a[0 : (n - 1)], # noqa: E203 + product[(0 + j) : (n - 1 + j)], # noqa: E203 + [product[n + j], product[n + j + 1]], + ) def inverse_quantum_multiplication(eng, quint_a, quint_b, product): @@ -514,12 +521,18 @@ def inverse_quantum_multiplication(eng, quint_a, quint_b, product): for j in range(2, n): with Control(eng, quint_b[j]): - SubtractQuantum | (quint_a[0:(n - 1)], product[(0 + j):( - n - 1 + j)], [product[n + j], product[n + j + 1]]) + SubtractQuantum | ( + quint_a[0 : (n - 1)], # noqa: E203 + product[(0 + j) : (n - 1 + j)], # noqa: E203 + [product[n + j], product[n + j + 1]], + ) for i in range(0, n): with Control(eng, [quint_a[i], quint_b[0]]): X | product[i] with Control(eng, quint_b[1]): - SubtractQuantum | (quint_a[0:(n - 1)], product[1:n], - [product[n + 1], product[n + 2]]) + SubtractQuantum | ( + quint_a[0 : (n - 1)], # noqa: E203 + product[1:n], + [product[n + 1], product[n + 2]], + ) diff --git a/projectq/libs/math/_quantummath_test.py b/projectq/libs/math/_quantummath_test.py index f3c8dfee5..ac936691a 100644 --- a/projectq/libs/math/_quantummath_test.py +++ b/projectq/libs/math/_quantummath_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,11 +16,9 @@ import pytest from projectq import MainEngine -from projectq.cengines import (InstructionFilter, AutoReplacer, - DecompositionRuleSet) +from projectq.cengines import InstructionFilter, AutoReplacer, DecompositionRuleSet from projectq.backends import Simulator -from projectq.ops import (All, BasicMathGate, ClassicalInstructionGate, - Measure, X) +from projectq.ops import All, BasicMathGate, ClassicalInstructionGate, Measure, X from projectq.setups.decompositions import swap2cnot @@ -38,12 +37,12 @@ def print_all_probabilities(eng, qureg): i = 0 y = len(qureg) - while i < (2**y): + while i < (2 ** y): qubit_list = [int(x) for x in list(('{0:0b}'.format(i)).zfill(y))] qubit_list = qubit_list[::-1] - l = eng.backend.get_probability(qubit_list, qureg) - if l != 0.0: - print(l, qubit_list, i) + prob = eng.backend.get_probability(qubit_list, qureg) + if prob != 0.0: + print(prob, qubit_list, i) i += 1 @@ -69,11 +68,10 @@ def no_math_emulation(eng, cmd): @pytest.fixture def eng(): - return MainEngine(backend=Simulator(), - engine_list=[ - AutoReplacer(rule_set), - InstructionFilter(no_math_emulation) - ]) + return MainEngine( + backend=Simulator(), + engine_list=[AutoReplacer(rule_set), InstructionFilter(no_math_emulation)], + ) def test_quantum_adder(eng): @@ -83,28 +81,22 @@ def test_quantum_adder(eng): init(eng, qureg_a, 2) init(eng, qureg_b, 1) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 0, 0], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 0, 0], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 0, 0], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 0, 0], qureg_b)) with Control(eng, control_qubit): AddQuantum | (qureg_a, qureg_b) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 0, 0], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 0, 0], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 0, 0], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 0, 0], qureg_b)) X | control_qubit with Control(eng, control_qubit): AddQuantum | (qureg_a, qureg_b) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 0, 0], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 0, 0], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 0, 0], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 0, 0], qureg_b)) init(eng, qureg_a, 2) # reset init(eng, qureg_b, 3) # reset @@ -115,38 +107,30 @@ def test_quantum_adder(eng): AddQuantum | (qureg_a, qureg_b, c) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 1, 1], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 1, 1], qureg_b)) - assert 1. == pytest.approx(eng.backend.get_probability([1], c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 1, 1], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 1, 1], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1], c)) with Compute(eng): with Control(eng, control_qubit): AddQuantum | (qureg_a, qureg_b) Uncompute(eng) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 1, 1], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 1, 1], qureg_b)) - assert 1. == pytest.approx(eng.backend.get_probability([1], c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 1, 1], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 1, 1], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1], c)) AddQuantum | (qureg_a, qureg_b) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 1, 1], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 1, 1], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 1, 1], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 1, 1], qureg_b)) with Compute(eng): AddQuantum | (qureg_a, qureg_b) Uncompute(eng) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 1, 1], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 1, 1], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 1, 1], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 1, 1], qureg_b)) d = eng.allocate_qureg(2) @@ -155,11 +139,9 @@ def test_quantum_adder(eng): AddQuantum | (qureg_a, qureg_b, d) Uncompute(eng) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 1, 1], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 1, 1], qureg_b)) - assert 1. == pytest.approx(eng.backend.get_probability([0, 0], d)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 1, 1], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 1, 1], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0], d)) All(Measure) | qureg_b Measure | c @@ -177,10 +159,8 @@ def test_quantumsubtraction(eng): with Control(eng, control_qubit): SubtractQuantum | (qureg_a, qureg_b) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 1, 0, 0], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 0, 0, 0], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 1, 0, 0], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 0, 0, 0], qureg_b)) init(eng, qureg_a, 5) # reset init(eng, qureg_b, 2) # reset @@ -191,10 +171,8 @@ def test_quantumsubtraction(eng): SubtractQuantum | (qureg_a, qureg_b) print_all_probabilities(eng, qureg_b) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 1, 0, 0], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 1, 1, 1, 0], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 1, 0, 0], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 1, 1, 0], qureg_b)) init(eng, qureg_a, 5) # reset init(eng, qureg_b, 14) # reset @@ -205,10 +183,8 @@ def test_quantumsubtraction(eng): SubtractQuantum | (qureg_a, qureg_b) Uncompute(eng) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 1, 0, 0], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 0, 0, 0], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 1, 0, 0], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 0, 0, 0], qureg_b)) All(Measure) | qureg_a All(Measure) | qureg_b @@ -223,7 +199,7 @@ def test_comparator(eng): ComparatorQuantum | (qureg_a, qureg_b, compare_qubit) - assert 1. == pytest.approx(eng.backend.get_probability([1], compare_qubit)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1], compare_qubit)) All(Measure) | qureg_a All(Measure) | qureg_b @@ -240,12 +216,9 @@ def test_quantumdivision(eng): DivideQuantum | (qureg_a, qureg_b, qureg_c) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 0, 0], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 0, 0], qureg_b)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 0, 0], qureg_c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 0, 0], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 0, 0], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 0, 0], qureg_c)) All(Measure) | qureg_a All(Measure) | qureg_b @@ -260,12 +233,9 @@ def test_quantumdivision(eng): DivideQuantum | (qureg_a, qureg_b, qureg_c) Uncompute(eng) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 0, 1], qureg_a)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 0], qureg_b)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 1, 0, 0], qureg_c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 0, 1], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 0], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 0, 0], qureg_c)) All(Measure) | qureg_a All(Measure) | qureg_b @@ -282,10 +252,9 @@ def test_quantummultiplication(eng): MultiplyQuantum | (qureg_a, qureg_b, qureg_c) - assert 1. == pytest.approx(eng.backend.get_probability([1, 1, 1], qureg_a)) - assert 1. == pytest.approx(eng.backend.get_probability([1, 1, 0], qureg_b)) - assert 1. == pytest.approx( - eng.backend.get_probability([1, 0, 1, 0, 1, 0, 0], qureg_c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 1], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 0], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 1, 0, 1, 0, 0], qureg_c)) All(Measure) | qureg_a All(Measure) | qureg_b @@ -295,8 +264,7 @@ def test_quantummultiplication(eng): init(eng, qureg_b, 3) init(eng, qureg_c, 21) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 0, 0, 0, 0], qureg_c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 0, 0, 0, 0], qureg_c)) init(eng, qureg_a, 2) init(eng, qureg_b, 3) @@ -304,7 +272,6 @@ def test_quantummultiplication(eng): MultiplyQuantum | (qureg_a, qureg_b, qureg_c) Uncompute(eng) - assert 1. == pytest.approx(eng.backend.get_probability([0, 1, 0], qureg_a)) - assert 1. == pytest.approx(eng.backend.get_probability([1, 1, 0], qureg_b)) - assert 1. == pytest.approx( - eng.backend.get_probability([0, 0, 0, 0, 0, 0, 0], qureg_c)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 0], qureg_a)) + assert 1.0 == pytest.approx(eng.backend.get_probability([1, 1, 0], qureg_b)) + assert 1.0 == pytest.approx(eng.backend.get_probability([0, 0, 0, 0, 0, 0, 0], qureg_c)) diff --git a/projectq/libs/revkit/__init__.py b/projectq/libs/revkit/__init__.py index af8c55ae6..8f422f811 100644 --- a/projectq/libs/revkit/__init__.py +++ b/projectq/libs/revkit/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/libs/revkit/_control_function.py b/projectq/libs/revkit/_control_function.py index 87f4e3804..a64e749eb 100644 --- a/projectq/libs/revkit/_control_function.py +++ b/projectq/libs/revkit/_control_function.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,6 +58,7 @@ def __init__(self, function, **kwargs): else: try: import dormouse + self.function = dormouse.to_truth_table(function) except ImportError: # pragma: no cover raise RuntimeError( @@ -83,7 +85,8 @@ def __or__(self, qubits): except ImportError: # pragma: no cover raise RuntimeError( "The RevKit Python library needs to be installed and in the " - "PYTHONPATH in order to call this function") + "PYTHONPATH in order to call this function" + ) # convert qubits to tuple qs = [] @@ -92,12 +95,11 @@ def __or__(self, qubits): # function truth table cannot be larger than number of control qubits # allow - if 2**(2**(len(qs) - 1)) <= self.function: - raise AttributeError( - "Function truth table exceeds number of control qubits") + if 2 ** (2 ** (len(qs) - 1)) <= self.function: + raise AttributeError("Function truth table exceeds number of control qubits") # create truth table from function integer - hex_length = max(2**(len(qs) - 1) // 4, 1) + hex_length = max(2 ** (len(qs) - 1) // 4, 1) revkit.tt(table="{0:#0{1}x}".format(self.function, hex_length)) # create reversible circuit from truth table @@ -105,8 +107,7 @@ def __or__(self, qubits): # check whether circuit has correct signature if revkit.ps(mct=True, silent=True)['qubits'] != len(qs): - raise RuntimeError("Generated circuit lines does not match " - "provided qubits") + raise RuntimeError("Generated circuit lines does not match provided qubits") # convert reversible circuit to ProjectQ code and execute it _exec(revkit.to_projectq(mct=True), qs) diff --git a/projectq/libs/revkit/_control_function_test.py b/projectq/libs/revkit/_control_function_test.py index 84ee3cc41..9c547a583 100644 --- a/projectq/libs/revkit/_control_function_test.py +++ b/projectq/libs/revkit/_control_function_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,46 +12,41 @@ # 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 libs.revkit._control_function.""" import pytest -from projectq.types import Qubit from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.libs.revkit import ControlFunctionOracle - # run this test only if RevKit Python module can be loaded revkit = pytest.importorskip('revkit') def test_control_function_majority(): saving_backend = DummyEngine(save_commands=True) - main_engine = MainEngine(backend=saving_backend, - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=saving_backend, engine_list=[DummyEngine()]) qubit0 = main_engine.allocate_qubit() qubit1 = main_engine.allocate_qubit() qubit2 = main_engine.allocate_qubit() qubit3 = main_engine.allocate_qubit() - ControlFunctionOracle(0xe8) | (qubit0, qubit1, qubit2, qubit3) + ControlFunctionOracle(0xE8) | (qubit0, qubit1, qubit2, qubit3) assert len(saving_backend.received_commands) == 7 def test_control_function_majority_from_python(): - dormouse = pytest.importorskip('dormouse') + dormouse = pytest.importorskip('dormouse') # noqa: F841 def maj(a, b, c): return (a and b) or (a and c) or (b and c) # pragma: no cover saving_backend = DummyEngine(save_commands=True) - main_engine = MainEngine(backend=saving_backend, - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=saving_backend, engine_list=[DummyEngine()]) qubit0 = main_engine.allocate_qubit() qubit1 = main_engine.allocate_qubit() @@ -61,8 +57,7 @@ def maj(a, b, c): def test_control_function_invalid_function(): - main_engine = MainEngine(backend=DummyEngine(), - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qureg = main_engine.allocate_qureg(3) @@ -70,7 +65,7 @@ def test_control_function_invalid_function(): ControlFunctionOracle(-42) | qureg with pytest.raises(AttributeError): - ControlFunctionOracle(0x8e) | qureg + ControlFunctionOracle(0x8E) | qureg with pytest.raises(RuntimeError): ControlFunctionOracle(0x8, synth=revkit.esopps) | qureg diff --git a/projectq/libs/revkit/_permutation.py b/projectq/libs/revkit/_permutation.py index 160f027d2..cc4891f95 100644 --- a/projectq/libs/revkit/_permutation.py +++ b/projectq/libs/revkit/_permutation.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -63,16 +64,16 @@ def __or__(self, qubits): except ImportError: # pragma: no cover raise RuntimeError( "The RevKit Python library needs to be installed and in the " - "PYTHONPATH in order to call this function") + "PYTHONPATH in order to call this function" + ) # convert qubits to flattened list qs = BasicGate.make_tuple_of_qureg(qubits) qs = sum(qs, []) # permutation must have 2*q elements, where q is the number of qubits - if 2**(len(qs)) != len(self.permutation): - raise AttributeError( - "Number of qubits does not fit to the size of the permutation") + if 2 ** (len(qs)) != len(self.permutation): + raise AttributeError("Number of qubits does not fit to the size of the permutation") # create reversible truth table from permutation revkit.perm(permutation=" ".join(map(str, self.permutation))) @@ -89,6 +90,5 @@ def _check_permutation(self): """ # permutation must start from 0, has no duplicates and all elements are # consecutive - if (sorted(list(set(self.permutation))) != - list(range(len(self.permutation)))): + if sorted(list(set(self.permutation))) != list(range(len(self.permutation))): raise AttributeError("Invalid permutation (does it start from 0?)") diff --git a/projectq/libs/revkit/_permutation_test.py b/projectq/libs/revkit/_permutation_test.py index eb0cf9bd5..57c92721a 100644 --- a/projectq/libs/revkit/_permutation_test.py +++ b/projectq/libs/revkit/_permutation_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,26 +12,22 @@ # 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 libs.revkit._permutation.""" import pytest -from projectq.types import Qubit from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.libs.revkit import PermutationOracle - # run this test only if RevKit Python module can be loaded revkit = pytest.importorskip('revkit') def test_basic_permutation(): saving_backend = DummyEngine(save_commands=True) - main_engine = MainEngine(backend=saving_backend, - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=saving_backend, engine_list=[DummyEngine()]) qubit0 = main_engine.allocate_qubit() qubit1 = main_engine.allocate_qubit() @@ -41,8 +38,7 @@ def test_basic_permutation(): def test_invalid_permutation(): - main_engine = MainEngine(backend=DummyEngine(), - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qubit0 = main_engine.allocate_qubit() qubit1 = main_engine.allocate_qubit() @@ -65,14 +61,15 @@ def test_invalid_permutation(): def test_synthesis_with_adjusted_tbs(): saving_backend = DummyEngine(save_commands=True) - main_engine = MainEngine(backend=saving_backend, - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=saving_backend, engine_list=[DummyEngine()]) qubit0 = main_engine.allocate_qubit() qubit1 = main_engine.allocate_qubit() - import revkit - synth = lambda: revkit.tbs() + def synth(): + import revkit + + return revkit.tbs() PermutationOracle([0, 2, 1, 3], synth=synth) | (qubit0, qubit1) @@ -81,14 +78,14 @@ def test_synthesis_with_adjusted_tbs(): def test_synthesis_with_synthesis_script(): saving_backend = DummyEngine(save_commands=True) - main_engine = MainEngine(backend=saving_backend, - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=saving_backend, engine_list=[DummyEngine()]) qubit0 = main_engine.allocate_qubit() qubit1 = main_engine.allocate_qubit() def synth(): import revkit + revkit.tbs() PermutationOracle([0, 2, 1, 3], synth=synth) | (qubit0, qubit1) diff --git a/projectq/libs/revkit/_phase.py b/projectq/libs/revkit/_phase.py index 8a9e2ab24..f30d72191 100644 --- a/projectq/libs/revkit/_phase.py +++ b/projectq/libs/revkit/_phase.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -61,6 +62,7 @@ def __init__(self, function, **kwargs): else: try: import dormouse + self.function = dormouse.to_truth_table(function) except ImportError: # pragma: no cover raise RuntimeError( @@ -85,7 +87,8 @@ def __or__(self, qubits): except ImportError: # pragma: no cover raise RuntimeError( "The RevKit Python library needs to be installed and in the " - "PYTHONPATH in order to call this function") + "PYTHONPATH in order to call this function" + ) # convert qubits to tuple qs = [] @@ -94,12 +97,11 @@ def __or__(self, qubits): # function truth table cannot be larger than number of control qubits # allow - if 2**(2**len(qs)) <= self.function: - raise AttributeError( - "Function truth table exceeds number of control qubits") + if 2 ** (2 ** len(qs)) <= self.function: + raise AttributeError("Function truth table exceeds number of control qubits") # create truth table from function integer - hex_length = max(2**(len(qs) - 1) // 4, 1) + hex_length = max(2 ** (len(qs) - 1) // 4, 1) revkit.tt(table="{0:#0{1}x}".format(self.function, hex_length)) # create phase circuit from truth table @@ -107,8 +109,7 @@ def __or__(self, qubits): # check whether circuit has correct signature if revkit.ps(mct=True, silent=True)['qubits'] != len(qs): - raise RuntimeError("Generated circuit lines does not match " - "provided qubits") + raise RuntimeError("Generated circuit lines does not match provided qubits") # convert reversible circuit to ProjectQ code and execute it _exec(revkit.to_projectq(mct=True), qs) diff --git a/projectq/libs/revkit/_phase_test.py b/projectq/libs/revkit/_phase_test.py index af634890e..aee988d11 100644 --- a/projectq/libs/revkit/_phase_test.py +++ b/projectq/libs/revkit/_phase_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,12 +12,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. - """Tests for libs.revkit._phase.""" import pytest -from projectq.types import Qubit from projectq import MainEngine from projectq.backends import Simulator from projectq.cengines import DummyEngine @@ -36,17 +35,16 @@ def test_phase_majority(): qureg = main_engine.allocate_qureg(3) All(H) | qureg - PhaseOracle(0xe8) | qureg + PhaseOracle(0xE8) | qureg main_engine.flush() - assert np.array_equal(np.sign(sim.cheat()[1]), - [1., 1., 1., -1., 1., -1., -1., -1.]) + assert np.array_equal(np.sign(sim.cheat()[1]), [1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0]) All(Measure) | qureg def test_phase_majority_from_python(): - dormouse = pytest.importorskip('dormouse') + dormouse = pytest.importorskip('dormouse') # noqa: F841 def maj(a, b, c): return (a and b) or (a and c) or (b and c) # pragma: no cover @@ -60,14 +58,12 @@ def maj(a, b, c): main_engine.flush() - assert np.array_equal(np.sign(sim.cheat()[1]), - [1., 1., 1., -1., 1., -1., -1., -1.]) + assert np.array_equal(np.sign(sim.cheat()[1]), [1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0]) All(Measure) | qureg def test_phase_invalid_function(): - main_engine = MainEngine(backend=DummyEngine(), - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) qureg = main_engine.allocate_qureg(3) @@ -75,7 +71,7 @@ def test_phase_invalid_function(): PhaseOracle(-42) | qureg with pytest.raises(AttributeError): - PhaseOracle(0xcafe) | qureg + PhaseOracle(0xCAFE) | qureg with pytest.raises(RuntimeError): - PhaseOracle(0x8e, synth=lambda: revkit.esopbs()) | qureg + PhaseOracle(0x8E, synth=lambda: revkit.esopbs()) | qureg diff --git a/projectq/libs/revkit/_utils.py b/projectq/libs/revkit/_utils.py index d889e9409..5871d7d6d 100644 --- a/projectq/libs/revkit/_utils.py +++ b/projectq/libs/revkit/_utils.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,5 +23,4 @@ def _exec(code, qs): qs (tuple): Qubits to which the permutation is being applied. """ - from projectq.ops import C, X, Z, All exec(code) diff --git a/projectq/meta/__init__.py b/projectq/meta/__init__.py index a136d4124..adb5719e8 100755 --- a/projectq/meta/__init__.py +++ b/projectq/meta/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ The projectq.meta package features meta instructions which help both the user and the compiler in writing/producing efficient code. It includes, e.g., @@ -22,17 +22,10 @@ * Dagger (with Dagger(eng): ...) """ - from ._dirtyqubit import DirtyQubitTag -from ._loop import (LoopTag, - Loop) -from ._compute import (Compute, - Uncompute, - CustomUncompute, - ComputeTag, - UncomputeTag) -from ._control import (Control, - get_control_count) +from ._loop import LoopTag, Loop +from ._compute import Compute, Uncompute, CustomUncompute, ComputeTag, UncomputeTag +from ._control import Control, get_control_count from ._dagger import Dagger from ._util import insert_engine, drop_engine_after from ._logicalqubit import LogicalQubitIDTag diff --git a/projectq/meta/_compute.py b/projectq/meta/_compute.py index 5c624524a..d2adbf16a 100755 --- a/projectq/meta/_compute.py +++ b/projectq/meta/_compute.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Compute, Uncompute, CustomUncompute. @@ -37,6 +37,7 @@ class NoComputeSectionError(Exception): """ Exception raised if uncompute is called but no compute section found. """ + pass @@ -105,17 +106,11 @@ def run_uncompute(self): # No qubits allocated during Compute section -> do standard uncompute if len(self._allocated_qubit_ids) == 0: - self.send([self._add_uncompute_tag(cmd.get_inverse()) - for cmd in reversed(self._l)]) + self.send([self._add_uncompute_tag(cmd.get_inverse()) for cmd in reversed(self._l)]) return # qubits ids which were allocated and deallocated in Compute section - ids_local_to_compute = self._allocated_qubit_ids.intersection( - self._deallocated_qubit_ids) - # qubit ids which were allocated but not yet deallocated in - # Compute section - ids_still_alive = self._allocated_qubit_ids.difference( - self._deallocated_qubit_ids) + ids_local_to_compute = self._allocated_qubit_ids.intersection(self._deallocated_qubit_ids) # No qubits allocated and already deallocated during compute. # Don't inspect each command as below -> faster uncompute @@ -135,9 +130,7 @@ def run_uncompute(self): qubit_found = True break if not qubit_found: - raise QubitManagementError( - "\nQubit was not found in " + - "MainEngine.active_qubits.\n") + raise QubitManagementError("\nQubit was not found in " + "MainEngine.active_qubits.\n") self.send([self._add_uncompute_tag(cmd.get_inverse())]) else: self.send([self._add_uncompute_tag(cmd.get_inverse())]) @@ -148,19 +141,20 @@ def run_uncompute(self): for cmd in reversed(self._l): if cmd.gate == Deallocate: assert (cmd.qubits[0][0].id) in ids_local_to_compute + # Create new local qubit which lives within uncompute section # Allocate needs to have old tags + uncompute tag def add_uncompute(command, old_tags=deepcopy(cmd.tags)): command.tags = old_tags + [UncomputeTag()] return command + tagger_eng = projectq.cengines.CommandModifier(add_uncompute) insert_engine(self, tagger_eng) new_local_qb = self.allocate_qubit() drop_engine_after(self) - new_local_id[cmd.qubits[0][0].id] = deepcopy( - new_local_qb[0].id) + new_local_id[cmd.qubits[0][0].id] = deepcopy(new_local_qb[0].id) # Set id of new_local_qb to -1 such that it doesn't send a # deallocate gate new_local_qb[0].id = -1 @@ -188,9 +182,7 @@ def add_uncompute(command, old_tags=deepcopy(cmd.tags)): qubit_found = True break if not qubit_found: - raise QubitManagementError( - "\nQubit was not found in " + - "MainEngine.active_qubits.\n") + raise QubitManagementError("\nQubit was not found in " + "MainEngine.active_qubits.\n") self.send([self._add_uncompute_tag(cmd.get_inverse())]) else: @@ -218,17 +210,17 @@ def end_compute(self): section which has not been allocated in Compute section """ self._compute = False - if not self._allocated_qubit_ids.issuperset( - self._deallocated_qubit_ids): + if not self._allocated_qubit_ids.issuperset(self._deallocated_qubit_ids): raise QubitManagementError( "\nQubit has been deallocated in with Compute(eng) context \n" - "which has not been allocated within this Compute section") + "which has not been allocated within this Compute section" + ) def receive(self, command_list): """ - If in compute-mode: Receive commands and store deepcopy of each cmd. - Add ComputeTag to received cmd and send it on. - Otherwise: send all received commands directly to next_engine. + If in compute-mode, receive commands and store deepcopy of each cmd. + Add ComputeTag to received cmd and send it on. Otherwise, send all + received commands directly to next_engine. Args: command_list (list): List of commands to receive. @@ -251,6 +243,7 @@ class UncomputeEngine(BasicEngine): """ Adds Uncompute-tags to all commands. """ + def __init__(self): """ Initialize a UncomputeEngine. @@ -386,8 +379,8 @@ def __enter__(self): compute_eng = self.engine.next_engine if not isinstance(compute_eng, ComputeEngine): raise NoComputeSectionError( - "Invalid call to CustomUncompute: No corresponding" - "'with Compute' statement found.") + "Invalid call to CustomUncompute: No corresponding 'with Compute' statement found." + ) # Make copy so there is not reference to compute_eng anymore # after __enter__ self._allocated_qubit_ids = compute_eng._allocated_qubit_ids.copy() @@ -406,15 +399,14 @@ def __exit__(self, type, value, traceback): return # Check that all qubits allocated within Compute or within # CustomUncompute have been deallocated. - all_allocated_qubits = self._allocated_qubit_ids.union( - self._uncompute_eng._allocated_qubit_ids) - all_deallocated_qubits = self._deallocated_qubit_ids.union( - self._uncompute_eng._deallocated_qubit_ids) + all_allocated_qubits = self._allocated_qubit_ids.union(self._uncompute_eng._allocated_qubit_ids) + all_deallocated_qubits = self._deallocated_qubit_ids.union(self._uncompute_eng._deallocated_qubit_ids) if len(all_allocated_qubits.difference(all_deallocated_qubits)) != 0: raise QubitManagementError( - "\nError. Not all qubits have been deallocated which have \n" + - "been allocated in the with Compute(eng) or with " + - "CustomUncompute(eng) context.") + "\nError. Not all qubits have been deallocated which have \n" + + "been allocated in the with Compute(eng) or with " + + "CustomUncompute(eng) context." + ) # remove uncompute engine drop_engine_after(self.engine) @@ -433,8 +425,6 @@ def Uncompute(engine): """ compute_eng = engine.next_engine if not isinstance(compute_eng, ComputeEngine): - raise NoComputeSectionError("Invalid call to Uncompute: No " - "corresponding 'with Compute' statement " - "found.") + raise NoComputeSectionError("Invalid call to Uncompute: No corresponding 'with Compute' statement found.") compute_eng.run_uncompute() drop_engine_after(engine) diff --git a/projectq/meta/_compute_test.py b/projectq/meta/_compute_test.py index 223fefedc..66eec753d 100755 --- a/projectq/meta/_compute_test.py +++ b/projectq/meta/_compute_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.meta._compute.py""" import pytest @@ -21,7 +21,6 @@ from projectq import MainEngine from projectq.cengines import DummyEngine, CompareEngine from projectq.ops import H, Rx, Ry, Deallocate, Allocate, CNOT, NOT, FlushGate -from projectq.types import WeakQubitRef from projectq.meta import DirtyQubitTag from projectq.meta import _compute @@ -73,8 +72,7 @@ def test_compute_engine(): assert backend.received_commands[0].gate == Allocate assert backend.received_commands[0].tags == [_compute.ComputeTag()] assert backend.received_commands[1].gate == H - assert backend.received_commands[1].tags == [_compute.ComputeTag(), - "TagAddedLater"] + assert backend.received_commands[1].tags == [_compute.ComputeTag(), "TagAddedLater"] assert backend.received_commands[2].gate == Rx(0.6) assert backend.received_commands[2].tags == [_compute.ComputeTag()] assert backend.received_commands[3].gate == Deallocate @@ -220,10 +218,9 @@ def test_compute_uncompute_with_statement(): def allow_dirty_qubits(self, meta_tag): return meta_tag == DirtyQubitTag - dummy_cengine.is_meta_tag_handler = types.MethodType(allow_dirty_qubits, - dummy_cengine) - eng = MainEngine(backend=backend, - engine_list=[compare_engine0, dummy_cengine]) + + dummy_cengine.is_meta_tag_handler = types.MethodType(allow_dirty_qubits, dummy_cengine) + eng = MainEngine(backend=backend, engine_list=[compare_engine0, dummy_cengine]) qubit = eng.allocate_qubit() with _compute.Compute(eng): Rx(0.9) | qubit @@ -268,19 +265,21 @@ def allow_dirty_qubits(self, meta_tag): # Test that each command has correct tags assert backend.received_commands[0].tags == [] assert backend.received_commands[1].tags == [_compute.ComputeTag()] - assert backend.received_commands[2].tags == [DirtyQubitTag(), - _compute.ComputeTag()] + assert backend.received_commands[2].tags == [DirtyQubitTag(), _compute.ComputeTag()] for cmd in backend.received_commands[3:9]: assert cmd.tags == [_compute.ComputeTag()] - assert backend.received_commands[9].tags == [DirtyQubitTag(), - _compute.ComputeTag()] + assert backend.received_commands[9].tags == [DirtyQubitTag(), _compute.ComputeTag()] assert backend.received_commands[10].tags == [] - assert backend.received_commands[11].tags == [DirtyQubitTag(), - _compute.UncomputeTag()] + assert backend.received_commands[11].tags == [ + DirtyQubitTag(), + _compute.UncomputeTag(), + ] for cmd in backend.received_commands[12:18]: assert cmd.tags == [_compute.UncomputeTag()] - assert backend.received_commands[18].tags == [DirtyQubitTag(), - _compute.UncomputeTag()] + assert backend.received_commands[18].tags == [ + DirtyQubitTag(), + _compute.UncomputeTag(), + ] assert backend.received_commands[19].tags == [_compute.UncomputeTag()] assert backend.received_commands[20].tags == [] assert backend.received_commands[21].tags == [] @@ -295,8 +294,7 @@ def allow_dirty_qubits(self, meta_tag): assert backend.received_commands[4].qubits[0][0].id == qubit_id assert backend.received_commands[5].qubits[0][0].id == ancilla_compt_id assert backend.received_commands[6].qubits[0][0].id == qubit_id - assert (backend.received_commands[6].control_qubits[0].id == - ancilla_compt_id) + assert backend.received_commands[6].control_qubits[0].id == ancilla_compt_id assert backend.received_commands[7].qubits[0][0].id == qubit_id assert backend.received_commands[8].qubits[0][0].id == ancilla_compt_id assert backend.received_commands[9].qubits[0][0].id == ancilla_compt_id @@ -304,8 +302,7 @@ def allow_dirty_qubits(self, meta_tag): assert backend.received_commands[12].qubits[0][0].id == ancilla_uncompt_id assert backend.received_commands[13].qubits[0][0].id == qubit_id assert backend.received_commands[14].qubits[0][0].id == qubit_id - assert (backend.received_commands[14].control_qubits[0].id == - ancilla_uncompt_id) + assert backend.received_commands[14].control_qubits[0].id == ancilla_uncompt_id assert backend.received_commands[15].qubits[0][0].id == ancilla_uncompt_id assert backend.received_commands[16].qubits[0][0].id == qubit_id assert backend.received_commands[17].qubits[0][0].id == ancilla2_id @@ -324,10 +321,9 @@ def allow_dirty_qubits(self, meta_tag): def allow_dirty_qubits(self, meta_tag): return meta_tag == DirtyQubitTag - dummy_cengine1.is_meta_tag_handler = types.MethodType(allow_dirty_qubits, - dummy_cengine1) - eng1 = MainEngine(backend=backend1, - engine_list=[compare_engine1, dummy_cengine1]) + + dummy_cengine1.is_meta_tag_handler = types.MethodType(allow_dirty_qubits, dummy_cengine1) + eng1 = MainEngine(backend=backend1, engine_list=[compare_engine1, dummy_cengine1]) qubit = eng1.allocate_qubit() with _compute.Compute(eng1): Rx(0.9) | qubit @@ -361,7 +357,8 @@ def allow_dirty_qubits(self, meta_tag): def test_exception_if_no_compute_but_uncompute(): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) with pytest.raises(_compute.NoComputeSectionError): - with _compute.CustomUncompute(eng): pass + with _compute.CustomUncompute(eng): + pass def test_exception_if_no_compute_but_uncompute2(): @@ -373,7 +370,7 @@ def test_exception_if_no_compute_but_uncompute2(): def test_qubit_management_error(): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) with _compute.Compute(eng): - ancilla = eng.allocate_qubit() + ancilla = eng.allocate_qubit() # noqa: F841 eng.active_qubits = weakref.WeakSet() with pytest.raises(_compute.QubitManagementError): _compute.Uncompute(eng) @@ -382,7 +379,7 @@ def test_qubit_management_error(): def test_qubit_management_error2(): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) with _compute.Compute(eng): - ancilla = eng.allocate_qubit() + ancilla = eng.allocate_qubit() # noqa: F841 local_ancilla = eng.allocate_qubit() local_ancilla[0].__del__() eng.active_qubits = weakref.WeakSet() @@ -393,7 +390,7 @@ def test_qubit_management_error2(): def test_only_single_error_in_costum_uncompute(): eng = MainEngine(backend=DummyEngine(), engine_list=[]) with _compute.Compute(eng): - qb = eng.allocate_qubit() + eng.allocate_qubit() # Tests that QubitManagementError is not sent in addition with pytest.raises(RuntimeError): with _compute.CustomUncompute(eng): diff --git a/projectq/meta/_control.py b/projectq/meta/_control.py index 76ded4326..b50848574 100755 --- a/projectq/meta/_control.py +++ b/projectq/meta/_control.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Contains the tools to make an entire section of operations controlled. @@ -59,8 +59,7 @@ def _has_compute_uncompute_tag(self, cmd): return False def _handle_command(self, cmd): - if (not self._has_compute_uncompute_tag(cmd) and not - isinstance(cmd.gate, ClassicalInstructionGate)): + if not self._has_compute_uncompute_tag(cmd) and not isinstance(cmd.gate, ClassicalInstructionGate): cmd.add_control_qubits(self._qubits) self.send([cmd]) @@ -96,7 +95,7 @@ def __init__(self, engine, qubits): ... """ self.engine = engine - assert(not isinstance(qubits, tuple)) + assert not isinstance(qubits, tuple) if isinstance(qubits, BasicQubit): qubits = [qubits] self._qubits = qubits diff --git a/projectq/meta/_control_test.py b/projectq/meta/_control_test.py index 77bf538e4..601252e04 100755 --- a/projectq/meta/_control_test.py +++ b/projectq/meta/_control_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,17 +12,12 @@ # 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.meta._control.py""" from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.ops import Command, H, Rx -from projectq.meta import (DirtyQubitTag, - ComputeTag, - UncomputeTag, - Compute, - Uncompute) +from projectq.meta import DirtyQubitTag, ComputeTag, UncomputeTag, Compute, Uncompute from projectq.meta import _control diff --git a/projectq/meta/_dagger.py b/projectq/meta/_dagger.py index a27a48920..bbe2a3d54 100755 --- a/projectq/meta/_dagger.py +++ b/projectq/meta/_dagger.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Tools to easily invert a sequence of gates. @@ -48,18 +48,19 @@ def run(self): have been deallocated. """ if self._deallocated_qubit_ids != self._allocated_qubit_ids: - raise QubitManagementError( - "\n Error. Qubits have been allocated in 'with " + - "Dagger(eng)' context,\n which have not explicitely " + - "been deallocated.\n" + - "Correct usage:\n" + - "with Dagger(eng):\n" + - " qubit = eng.allocate_qubit()\n" + - " ...\n" + - " del qubit[0]\n") + raise QubitManagementError( + "\n Error. Qubits have been allocated in 'with " + + "Dagger(eng)' context,\n which have not explicitely " + + "been deallocated.\n" + + "Correct usage:\n" + + "with Dagger(eng):\n" + + " qubit = eng.allocate_qubit()\n" + + " ...\n" + + " del qubit[0]\n" + ) for cmd in reversed(self._commands): - self.send([cmd.get_inverse()]) + self.send([cmd.get_inverse()]) def receive(self, command_list): """ diff --git a/projectq/meta/_dagger_test.py b/projectq/meta/_dagger_test.py index d7a285e77..a91e51cb4 100755 --- a/projectq/meta/_dagger_test.py +++ b/projectq/meta/_dagger_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.meta._dagger.py""" import pytest @@ -59,7 +59,7 @@ def test_dagger_qubit_management_error(): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) with pytest.raises(_dagger.QubitManagementError): with _dagger.Dagger(eng): - ancilla = eng.allocate_qubit() + ancilla = eng.allocate_qubit() # noqa: F841 def test_dagger_raises_only_single_error(): @@ -67,5 +67,5 @@ def test_dagger_raises_only_single_error(): # Tests that QubitManagementError is not sent in addition with pytest.raises(RuntimeError): with _dagger.Dagger(eng): - ancilla = eng.allocate_qubit() + ancilla = eng.allocate_qubit() # noqa: F841 raise RuntimeError diff --git a/projectq/meta/_dirtyqubit.py b/projectq/meta/_dirtyqubit.py index e0693e8e6..3a3ec455a 100755 --- a/projectq/meta/_dirtyqubit.py +++ b/projectq/meta/_dirtyqubit.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Defines the DirtyQubitTag meta tag. """ @@ -21,6 +21,7 @@ class DirtyQubitTag(object): """ Dirty qubit meta tag """ + def __eq__(self, other): return isinstance(other, DirtyQubitTag) diff --git a/projectq/meta/_dirtyqubit_test.py b/projectq/meta/_dirtyqubit_test.py index c23b49ba5..3cfe437a5 100755 --- a/projectq/meta/_dirtyqubit_test.py +++ b/projectq/meta/_dirtyqubit_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.meta._dirtyqubit.py""" from projectq.meta import ComputeTag diff --git a/projectq/meta/_logicalqubit.py b/projectq/meta/_logicalqubit.py index 0e50ada9b..63ee60514 100644 --- a/projectq/meta/_logicalqubit.py +++ b/projectq/meta/_logicalqubit.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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 LogicalQubitIDTag to annotate a MeasureGate for mapped qubits. """ @@ -24,12 +24,12 @@ class LogicalQubitIDTag(object): Attributes: logical_qubit_id (int): Logical qubit id """ + def __init__(self, logical_qubit_id): self.logical_qubit_id = logical_qubit_id def __eq__(self, other): - return (isinstance(other, LogicalQubitIDTag) and - self.logical_qubit_id == other.logical_qubit_id) + return isinstance(other, LogicalQubitIDTag) and self.logical_qubit_id == other.logical_qubit_id def __ne__(self, other): return not self.__eq__(other) diff --git a/projectq/meta/_logicalqubit_test.py b/projectq/meta/_logicalqubit_test.py index 14f1f244d..c64a79837 100644 --- a/projectq/meta/_logicalqubit_test.py +++ b/projectq/meta/_logicalqubit_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.meta._logicalqubit.py.""" from copy import deepcopy diff --git a/projectq/meta/_loop.py b/projectq/meta/_loop.py index 1b7408232..d3e61ab5a 100755 --- a/projectq/meta/_loop.py +++ b/projectq/meta/_loop.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Tools to implement loops. @@ -38,14 +38,14 @@ class LoopTag(object): """ Loop meta tag """ + def __init__(self, num): self.num = num self.id = LoopTag.loop_tag_id LoopTag.loop_tag_id += 1 def __eq__(self, other): - return (isinstance(other, LoopTag) and self.id == other.id and - self.num == other.num) + return isinstance(other, LoopTag) and self.id == other.id and self.num == other.num def __ne__(self, other): return not self.__eq__(other) @@ -90,13 +90,15 @@ def run(self): is_meta_tag_supported(next_engine, LoopTag) == False """ - error_message = ("\n Error. Qubits have been allocated in with " - "Loop(eng, num) context,\n which have not " - "explicitely been deallocated in the Loop context.\n" - "Correct usage:\nwith Loop(eng, 5):\n" - " qubit = eng.allocate_qubit()\n" - " ...\n" - " del qubit[0]\n") + error_message = ( + "\n Error. Qubits have been allocated in with " + "Loop(eng, num) context,\n which have not " + "explicitely been deallocated in the Loop context.\n" + "Correct usage:\nwith Loop(eng, 5):\n" + " qubit = eng.allocate_qubit()\n" + " ...\n" + " del qubit[0]\n" + ) if not self._next_engines_support_loop_tag: # Unroll the loop # Check that local qubits have been deallocated: @@ -145,8 +147,7 @@ def receive(self, command_list): unroll or, if there is a LoopTag-handling engine, add the LoopTag. """ - if (self._next_engines_support_loop_tag or - self.next_engine.is_meta_tag_supported(LoopTag)): + if self._next_engines_support_loop_tag or self.next_engine.is_meta_tag_supported(LoopTag): # Loop tag is supported, send everything with a LoopTag # Don't check is_meta_tag_supported anymore self._next_engines_support_loop_tag = True @@ -167,25 +168,21 @@ def receive(self, command_list): if cmd.gate == Allocate: self._allocated_qubit_ids.add(cmd.qubits[0][0].id) # Save reference to this local qubit - self._refs_to_local_qb[cmd.qubits[0][0].id] = ( - [cmd.qubits[0][0]]) + self._refs_to_local_qb[cmd.qubits[0][0].id] = [cmd.qubits[0][0]] elif cmd.gate == Deallocate: self._deallocated_qubit_ids.add(cmd.qubits[0][0].id) # Save reference to this local qubit - self._refs_to_local_qb[cmd.qubits[0][0].id].append( - cmd.qubits[0][0]) + self._refs_to_local_qb[cmd.qubits[0][0].id].append(cmd.qubits[0][0]) else: # Add a reference to each place a local qubit id is # used as within either control_qubit or qubits for control_qubit in cmd.control_qubits: if control_qubit.id in self._allocated_qubit_ids: - self._refs_to_local_qb[control_qubit.id].append( - control_qubit) + self._refs_to_local_qb[control_qubit.id].append(control_qubit) for qureg in cmd.qubits: for qubit in qureg: if qubit.id in self._allocated_qubit_ids: - self._refs_to_local_qb[qubit.id].append( - qubit) + self._refs_to_local_qb[qubit.id].append(qubit) class Loop(object): diff --git a/projectq/meta/_loop_test.py b/projectq/meta/_loop_test.py index 2755a690c..3785f5891 100755 --- a/projectq/meta/_loop_test.py +++ b/projectq/meta/_loop_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.meta._loop.py""" import pytest @@ -19,7 +19,7 @@ from copy import deepcopy from projectq import MainEngine -from projectq.meta import ComputeTag, DirtyQubitTag +from projectq.meta import ComputeTag from projectq.cengines import DummyEngine from projectq.ops import H, CNOT, X, FlushGate, Allocate, Deallocate @@ -41,14 +41,12 @@ def test_loop_tag(): def test_loop_wrong_input_type(): eng = MainEngine(backend=DummyEngine(), engine_list=[]) - qubit = eng.allocate_qubit() with pytest.raises(TypeError): _loop.Loop(eng, 1.1) def test_loop_negative_iteration_number(): eng = MainEngine(backend=DummyEngine(), engine_list=[]) - qubit = eng.allocate_qubit() with pytest.raises(ValueError): _loop.Loop(eng, -1) @@ -58,7 +56,7 @@ def test_loop_with_supported_loop_tag_and_local_qubits(): eng = MainEngine(backend=backend, engine_list=[DummyEngine()]) def allow_loop_tags(self, meta_tag): - return meta_tag == _loop.LoopTag + return meta_tag == _loop.LoopTag backend.is_meta_tag_handler = types.MethodType(allow_loop_tags, backend) qubit = eng.allocate_qubit() @@ -135,7 +133,7 @@ def test_empty_loop_when_loop_tag_supported_by_backend(): eng = MainEngine(backend=backend, engine_list=[DummyEngine()]) def allow_loop_tags(self, meta_tag): - return meta_tag == _loop.LoopTag + return meta_tag == _loop.LoopTag backend.is_meta_tag_handler = types.MethodType(allow_loop_tags, backend) qubit = eng.allocate_qubit() @@ -152,7 +150,7 @@ def test_loop_with_supported_loop_tag_depending_on_num(): eng = MainEngine(backend=backend, engine_list=[DummyEngine()]) def allow_loop_tags(self, meta_tag): - return meta_tag == _loop.LoopTag + return meta_tag == _loop.LoopTag backend.is_meta_tag_handler = types.MethodType(allow_loop_tags, backend) qubit = eng.allocate_qubit() @@ -193,29 +191,31 @@ def test_loop_unrolling_with_ancillas(): assert backend.received_commands[ii * 4 + 3].gate == X assert backend.received_commands[ii * 4 + 4].gate == Deallocate # Check qubit ids - assert (backend.received_commands[ii * 4 + 1].qubits[0][0].id == - backend.received_commands[ii * 4 + 2].qubits[0][0].id) - assert (backend.received_commands[ii * 4 + 1].qubits[0][0].id == - backend.received_commands[ii * 4 + 3].control_qubits[0].id) - assert (backend.received_commands[ii * 4 + 3].qubits[0][0].id == - qubit_id) - assert (backend.received_commands[ii * 4 + 1].qubits[0][0].id == - backend.received_commands[ii * 4 + 4].qubits[0][0].id) + assert ( + backend.received_commands[ii * 4 + 1].qubits[0][0].id + == backend.received_commands[ii * 4 + 2].qubits[0][0].id + ) + assert ( + backend.received_commands[ii * 4 + 1].qubits[0][0].id + == backend.received_commands[ii * 4 + 3].control_qubits[0].id + ) + assert backend.received_commands[ii * 4 + 3].qubits[0][0].id == qubit_id + assert ( + backend.received_commands[ii * 4 + 1].qubits[0][0].id + == backend.received_commands[ii * 4 + 4].qubits[0][0].id + ) assert backend.received_commands[13].gate == Deallocate assert backend.received_commands[14].gate == FlushGate() - assert (backend.received_commands[1].qubits[0][0].id != - backend.received_commands[5].qubits[0][0].id) - assert (backend.received_commands[1].qubits[0][0].id != - backend.received_commands[9].qubits[0][0].id) - assert (backend.received_commands[5].qubits[0][0].id != - backend.received_commands[9].qubits[0][0].id) + assert backend.received_commands[1].qubits[0][0].id != backend.received_commands[5].qubits[0][0].id + assert backend.received_commands[1].qubits[0][0].id != backend.received_commands[9].qubits[0][0].id + assert backend.received_commands[5].qubits[0][0].id != backend.received_commands[9].qubits[0][0].id def test_nested_loop(): backend = DummyEngine(save_commands=True) def allow_loop_tags(self, meta_tag): - return meta_tag == _loop.LoopTag + return meta_tag == _loop.LoopTag backend.is_meta_tag_handler = types.MethodType(allow_loop_tags, backend) eng = MainEngine(backend=backend, engine_list=[DummyEngine()]) @@ -229,8 +229,7 @@ def allow_loop_tags(self, meta_tag): assert len(backend.received_commands[1].tags) == 2 assert backend.received_commands[1].tags[0].num == 4 assert backend.received_commands[1].tags[1].num == 3 - assert (backend.received_commands[1].tags[0].id != - backend.received_commands[1].tags[1].id) + assert backend.received_commands[1].tags[0].id != backend.received_commands[1].tags[1].id def test_qubit_management_error(): @@ -238,17 +237,17 @@ def test_qubit_management_error(): eng = MainEngine(backend=backend, engine_list=[DummyEngine()]) with pytest.raises(_loop.QubitManagementError): with _loop.Loop(eng, 3): - qb = eng.allocate_qubit() + ancilla = eng.allocate_qubit() # noqa: F841 def test_qubit_management_error_when_loop_tag_supported(): backend = DummyEngine(save_commands=True) def allow_loop_tags(self, meta_tag): - return meta_tag == _loop.LoopTag + return meta_tag == _loop.LoopTag backend.is_meta_tag_handler = types.MethodType(allow_loop_tags, backend) eng = MainEngine(backend=backend, engine_list=[DummyEngine()]) with pytest.raises(_loop.QubitManagementError): with _loop.Loop(eng, 3): - qb = eng.allocate_qubit() + ancilla = eng.allocate_qubit() # noqa: F841 diff --git a/projectq/meta/_util.py b/projectq/meta/_util.py index 856ef6728..ffbc3dc20 100755 --- a/projectq/meta/_util.py +++ b/projectq/meta/_util.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/meta/_util_test.py b/projectq/meta/_util_test.py index 2b4ec892b..496d2f9b4 100755 --- a/projectq/meta/_util_test.py +++ b/projectq/meta/_util_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.meta import insert_engine, drop_engine_after diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index dd73cc2d5..79f50e32b 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,31 +13,34 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ._basics import (NotMergeable, - NotInvertible, - BasicGate, - MatrixGate, - SelfInverseGate, - BasicRotationGate, - ClassicalInstructionGate, - FastForwardingGate, - BasicMathGate, - BasicPhaseGate) +from ._basics import ( + NotMergeable, + NotInvertible, + BasicGate, + MatrixGate, + SelfInverseGate, + BasicRotationGate, + ClassicalInstructionGate, + FastForwardingGate, + BasicMathGate, + BasicPhaseGate, +) from ._command import apply_command, Command -from ._metagates import (DaggeredGate, - get_inverse, - is_identity, - ControlledGate, - C, - Tensor, - All) +from ._metagates import ( + DaggeredGate, + get_inverse, + is_identity, + ControlledGate, + C, + Tensor, + All, +) from ._gates import * from ._qftgate import QFT, QFTGate from ._qubit_operator import QubitOperator from ._shortcuts import * from ._time_evolution import TimeEvolution -from ._uniformly_controlled_rotation import (UniformlyControlledRy, - UniformlyControlledRz) +from ._uniformly_controlled_rotation import UniformlyControlledRy, UniformlyControlledRz from ._state_prep import StatePreparation from ._qpegate import QPE from ._qaagate import QAA diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index c7bdd31bc..6d224b98b 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,7 +42,7 @@ import unicodedata ANGLE_PRECISION = 12 -ANGLE_TOLERANCE = 10**-ANGLE_PRECISION +ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION RTOL = 1e-10 ATOL = 1e-12 @@ -51,6 +52,7 @@ class NotMergeable(Exception): Exception thrown when trying to merge two gates which are not mergeable (or where it is not implemented (yet)). """ + pass @@ -59,6 +61,7 @@ class NotInvertible(Exception): Exception thrown when trying to invert a gate which is not invertable (or where the inverse is not implemented (yet)). """ + pass @@ -66,6 +69,7 @@ class BasicGate(object): """ Base class of all gates. (Don't use it directly but derive from it) """ + def __init__(self): """ Initialize a basic gate. @@ -157,7 +161,7 @@ def make_tuple_of_qureg(qubits): (or list of Qubits) objects. """ if not isinstance(qubits, tuple): - qubits = (qubits, ) + qubits = (qubits,) qubits = list(qubits) @@ -257,6 +261,7 @@ class MatrixGate(BasicGate): gate = MatrixGate([[0, 1], [1, 0]]) gate | qubit """ + def __init__(self, matrix=None): """ Initialize MatrixGate @@ -284,19 +289,16 @@ def __eq__(self, other): """ if not hasattr(other, 'matrix'): return False - if (not isinstance(self.matrix, np.matrix) - or not isinstance(other.matrix, np.matrix)): - raise TypeError("One of the gates doesn't have the correct " - "type (numpy.matrix) for the matrix " - "attribute.") - if (self.matrix.shape == other.matrix.shape and np.allclose( - self.matrix, other.matrix, rtol=RTOL, atol=ATOL, - equal_nan=False)): + if not isinstance(self.matrix, np.matrix) or not isinstance(other.matrix, np.matrix): + raise TypeError("One of the gates doesn't have the correct type (numpy.matrix) for the matrix attribute.") + if self.matrix.shape == other.matrix.shape and np.allclose( + self.matrix, other.matrix, rtol=RTOL, atol=ATOL, equal_nan=False + ): return True return False def __str__(self): - return ("MatrixGate(" + str(self.matrix.tolist()) + ")") + return "MatrixGate(" + str(self.matrix.tolist()) + ")" def __hash__(self): return hash(str(self)) @@ -318,6 +320,7 @@ class SelfInverseGate(BasicGate): # get_inverse(H) == H, it is a self-inverse gate: get_inverse(H) | qubit """ + def get_inverse(self): return deepcopy(self) @@ -332,6 +335,7 @@ class BasicRotationGate(BasicGate): 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. @@ -340,9 +344,9 @@ def __init__(self, angle): angle (float): Angle of rotation (saved modulo 4 * pi) """ BasicGate.__init__(self) - rounded_angle = round(float(angle) % (4. * math.pi), ANGLE_PRECISION) + rounded_angle = round(float(angle) % (4.0 * math.pi), ANGLE_PRECISION) if rounded_angle > 4 * math.pi - ANGLE_TOLERANCE: - rounded_angle = 0. + rounded_angle = 0.0 self.angle = rounded_angle def __str__(self): @@ -367,8 +371,7 @@ def to_string(self, symbols=False): written in radian otherwise. """ if symbols: - angle = ("(" + str(round(self.angle / math.pi, 3)) - + unicodedata.lookup("GREEK SMALL LETTER PI") + ")") + 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 @@ -383,8 +386,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(round(self.angle / math.pi, 3)) + "\\pi}$" def get_inverse(self): """ @@ -418,7 +420,7 @@ def get_merged(self, other): raise NotMergeable("Can't merge different types of rotation gates.") def __eq__(self, other): - """ Return True if same class and same rotation angle. """ + """Return True if same class and same rotation angle.""" if isinstance(other, self.__class__): return self.angle == other.angle else: @@ -434,7 +436,7 @@ 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 + return self.angle == 0.0 or self.angle == 4 * math.pi class BasicPhaseGate(BasicGate): @@ -447,6 +449,7 @@ class BasicPhaseGate(BasicGate): 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. @@ -455,9 +458,9 @@ def __init__(self, angle): angle (float): Angle of rotation (saved modulo 2 * pi) """ BasicGate.__init__(self) - rounded_angle = round(float(angle) % (2. * math.pi), ANGLE_PRECISION) + rounded_angle = round(float(angle) % (2.0 * math.pi), ANGLE_PRECISION) if rounded_angle > 2 * math.pi - ANGLE_TOLERANCE: - rounded_angle = 0. + rounded_angle = 0.0 self.angle = rounded_angle def __str__(self): @@ -516,7 +519,7 @@ def get_merged(self, other): raise NotMergeable("Can't merge different types of rotation gates.") def __eq__(self, other): - """ Return True if same class and same rotation angle. """ + """Return True if same class and same rotation angle.""" if isinstance(other, self.__class__): return self.angle == other.angle else: @@ -537,6 +540,7 @@ class ClassicalInstructionGate(BasicGate): Base class for all gates which are not quantum gates in the typical sense, e.g., measurement, allocation/deallocation, ... """ + pass @@ -562,6 +566,7 @@ class FastForwardingGate(ClassicalInstructionGate): is required before the circuit gets sent through the API. """ + pass @@ -589,6 +594,7 @@ def add(x): def multiply(a,b,c) return (a,b,c+a*b) """ + def __init__(self, math_fun): """ Initialize a BasicMathGate by providing the mathematical function that diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index a58a24e4c..a47a28441 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -48,13 +48,13 @@ def test_basic_gate_make_tuple_of_qureg(main_engine): qubit3 = Qubit(main_engine, 3) qureg = Qureg([qubit2, qubit3]) case1 = _basics.BasicGate.make_tuple_of_qureg(qubit0) - assert case1 == ([qubit0], ) + assert case1 == ([qubit0],) case2 = _basics.BasicGate.make_tuple_of_qureg([qubit0, qubit1]) - assert case2 == ([qubit0, qubit1], ) + assert case2 == ([qubit0, qubit1],) case3 = _basics.BasicGate.make_tuple_of_qureg(qureg) - assert case3 == (qureg, ) - case4 = _basics.BasicGate.make_tuple_of_qureg((qubit0, )) - assert case4 == ([qubit0], ) + assert case3 == (qureg,) + case4 = _basics.BasicGate.make_tuple_of_qureg((qubit0,)) + assert case4 == ([qubit0],) case5 = _basics.BasicGate.make_tuple_of_qureg((qureg, qubit0)) assert case5 == (qureg, [qubit0]) @@ -67,21 +67,20 @@ def test_basic_gate_generate_command(main_engine): qureg = Qureg([qubit2, qubit3]) basic_gate = _basics.BasicGate() command1 = basic_gate.generate_command(qubit0) - assert command1 == Command(main_engine, basic_gate, ([qubit0], )) + assert command1 == Command(main_engine, basic_gate, ([qubit0],)) command2 = basic_gate.generate_command([qubit0, qubit1]) - assert command2 == Command(main_engine, basic_gate, ([qubit0, qubit1], )) + assert command2 == Command(main_engine, basic_gate, ([qubit0, qubit1],)) command3 = basic_gate.generate_command(qureg) - assert command3 == Command(main_engine, basic_gate, (qureg, )) - command4 = basic_gate.generate_command((qubit0, )) - assert command4 == Command(main_engine, basic_gate, ([qubit0], )) + assert command3 == Command(main_engine, basic_gate, (qureg,)) + command4 = basic_gate.generate_command((qubit0,)) + assert command4 == Command(main_engine, basic_gate, ([qubit0],)) command5 = basic_gate.generate_command((qureg, qubit0)) assert command5 == Command(main_engine, basic_gate, (qureg, [qubit0])) def test_basic_gate_or(): saving_backend = DummyEngine(save_commands=True) - main_engine = MainEngine(backend=saving_backend, - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=saving_backend, engine_list=[DummyEngine()]) qubit0 = Qubit(main_engine, 0) qubit1 = Qubit(main_engine, 1) qubit2 = Qubit(main_engine, 2) @@ -94,8 +93,8 @@ def test_basic_gate_or(): basic_gate | [qubit0, qubit1] command3 = basic_gate.generate_command(qureg) basic_gate | qureg - command4 = basic_gate.generate_command((qubit0, )) - basic_gate | (qubit0, ) + command4 = basic_gate.generate_command((qubit0,)) + basic_gate | (qubit0,) command5 = basic_gate.generate_command((qureg, qubit0)) basic_gate | (qureg, qubit0) received_commands = [] @@ -103,9 +102,7 @@ def test_basic_gate_or(): for cmd in saving_backend.received_commands: if not isinstance(cmd.gate, _basics.FastForwardingGate): received_commands.append(cmd) - assert received_commands == ([ - command1, command2, command3, command4, command5 - ]) + assert received_commands == ([command1, command2, command3, command4, command5]) def test_basic_gate_compare(): @@ -148,9 +145,15 @@ def test_self_inverse_gate(): assert id(self_inverse_gate.get_inverse()) != id(self_inverse_gate) -@pytest.mark.parametrize("input_angle, modulo_angle", - [(2.0, 2.0), (17., 4.4336293856408275), - (-0.5 * math.pi, 3.5 * math.pi), (4 * math.pi, 0)]) +@pytest.mark.parametrize( + "input_angle, modulo_angle", + [ + (2.0, 2.0), + (17.0, 4.4336293856408275), + (-0.5 * math.pi, 3.5 * math.pi), + (4 * math.pi, 0), + ], +) def test_basic_rotation_gate_init(input_angle, modulo_angle): # Test internal representation gate = _basics.BasicRotationGate(input_angle) @@ -171,8 +174,7 @@ def test_basic_rotation_tex_str(): assert gate.tex_str() == "BasicRotationGate$_{0.0\\pi}$" -@pytest.mark.parametrize("input_angle, inverse_angle", - [(2.0, -2.0 + 4 * math.pi), (-0.5, 0.5), (0.0, 0)]) +@pytest.mark.parametrize("input_angle, inverse_angle", [(2.0, -2.0 + 4 * math.pi), (-0.5, 0.5), (0.0, 0)]) def test_basic_rotation_gate_get_inverse(input_angle, inverse_angle): basic_rotation_gate = _basics.BasicRotationGate(input_angle) inverse = basic_rotation_gate.get_inverse() @@ -192,11 +194,11 @@ def test_basic_rotation_gate_get_merged(): def test_basic_rotation_gate_is_identity(): - basic_rotation_gate1 = _basics.BasicRotationGate(0.) - basic_rotation_gate2 = _basics.BasicRotationGate(1. * math.pi) - basic_rotation_gate3 = _basics.BasicRotationGate(2. * math.pi) - basic_rotation_gate4 = _basics.BasicRotationGate(3. * math.pi) - basic_rotation_gate5 = _basics.BasicRotationGate(4. * math.pi) + basic_rotation_gate1 = _basics.BasicRotationGate(0.0) + basic_rotation_gate2 = _basics.BasicRotationGate(1.0 * math.pi) + basic_rotation_gate3 = _basics.BasicRotationGate(2.0 * math.pi) + basic_rotation_gate4 = _basics.BasicRotationGate(3.0 * math.pi) + basic_rotation_gate5 = _basics.BasicRotationGate(4.0 * math.pi) assert basic_rotation_gate1.is_identity() assert not basic_rotation_gate2.is_identity() assert not basic_rotation_gate3.is_identity() @@ -216,8 +218,8 @@ def test_basic_rotation_gate_comparison_and_hash(): # Test __ne__: assert basic_rotation_gate4 != basic_rotation_gate1 # Test one gate close to 4*pi the other one close to 0 - basic_rotation_gate5 = _basics.BasicRotationGate(1.e-13) - basic_rotation_gate6 = _basics.BasicRotationGate(4 * math.pi - 1.e-13) + basic_rotation_gate5 = _basics.BasicRotationGate(1.0e-13) + basic_rotation_gate6 = _basics.BasicRotationGate(4 * math.pi - 1.0e-13) assert basic_rotation_gate5 == basic_rotation_gate6 assert basic_rotation_gate6 == basic_rotation_gate5 assert hash(basic_rotation_gate5) == hash(basic_rotation_gate6) @@ -227,9 +229,15 @@ def test_basic_rotation_gate_comparison_and_hash(): assert basic_rotation_gate2 != _basics.BasicRotationGate(0.5 + 2 * math.pi) -@pytest.mark.parametrize("input_angle, modulo_angle", - [(2.0, 2.0), (17., 4.4336293856408275), - (-0.5 * math.pi, 1.5 * math.pi), (2 * math.pi, 0)]) +@pytest.mark.parametrize( + "input_angle, modulo_angle", + [ + (2.0, 2.0), + (17.0, 4.4336293856408275), + (-0.5 * math.pi, 1.5 * math.pi), + (2 * math.pi, 0), + ], +) def test_basic_phase_gate_init(input_angle, modulo_angle): # Test internal representation gate = _basics.BasicPhaseGate(input_angle) @@ -248,8 +256,7 @@ def test_basic_phase_tex_str(): assert basic_rotation_gate.tex_str() == "BasicPhaseGate$_{0.0}$" -@pytest.mark.parametrize("input_angle, inverse_angle", - [(2.0, -2.0 + 2 * math.pi), (-0.5, 0.5), (0.0, 0)]) +@pytest.mark.parametrize("input_angle, inverse_angle", [(2.0, -2.0 + 2 * math.pi), (-0.5, 0.5), (0.0, 0)]) def test_basic_phase_gate_get_inverse(input_angle, inverse_angle): basic_phase_gate = _basics.BasicPhaseGate(input_angle) inverse = basic_phase_gate.get_inverse() @@ -280,8 +287,8 @@ def test_basic_phase_gate_comparison_and_hash(): # Test __ne__: assert basic_phase_gate4 != basic_phase_gate1 # Test one gate close to 2*pi the other one close to 0 - basic_phase_gate5 = _basics.BasicPhaseGate(1.e-13) - basic_phase_gate6 = _basics.BasicPhaseGate(2 * math.pi - 1.e-13) + basic_phase_gate5 = _basics.BasicPhaseGate(1.0e-13) + basic_phase_gate6 = _basics.BasicPhaseGate(2 * math.pi - 1.0e-13) assert basic_phase_gate5 == basic_phase_gate6 assert basic_phase_gate6 == basic_phase_gate5 assert hash(basic_phase_gate5) == hash(basic_phase_gate6) diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 317356c19..626b0b233 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -82,6 +83,7 @@ class Command(object): and hence adds its LoopTag to the end. all_qubits: A tuple of control_qubits + qubits """ + def __init__(self, engine, gate, qubits, controls=(), tags=()): """ Initialize a Command object. @@ -106,9 +108,7 @@ def __init__(self, engine, gate, qubits, controls=(), tags=()): Tags associated with the command. """ - qubits = tuple( - [WeakQubitRef(qubit.engine, qubit.id) for qubit in qreg] - for qreg in qubits) + qubits = tuple([WeakQubitRef(qubit.engine, qubit.id) for qubit in qreg] for qreg in qubits) self.gate = gate self.tags = list(tags) @@ -125,9 +125,14 @@ def qubits(self, qubits): self._qubits = self._order_qubits(qubits) def __deepcopy__(self, memo): - """ Deepcopy implementation. Engine should stay a reference.""" - return Command(self.engine, deepcopy(self.gate), self.qubits, - list(self.control_qubits), deepcopy(self.tags)) + """Deepcopy implementation. Engine should stay a reference.""" + return Command( + self.engine, + deepcopy(self.gate), + self.qubits, + list(self.control_qubits), + deepcopy(self.tags), + ) def get_inverse(self): """ @@ -140,9 +145,13 @@ def get_inverse(self): NotInvertible: If the gate does not provide an inverse (see BasicGate.get_inverse) """ - return Command(self._engine, projectq.ops.get_inverse(self.gate), - self.qubits, list(self.control_qubits), - deepcopy(self.tags)) + return Command( + self._engine, + projectq.ops.get_inverse(self.gate), + self.qubits, + list(self.control_qubits), + deepcopy(self.tags), + ) def is_identity(self): """ @@ -165,11 +174,14 @@ def get_merged(self, other): NotMergeable: if the gates don't supply a get_merged()-function or can't be merged for other reasons. """ - if (self.tags == other.tags and self.all_qubits == other.all_qubits - and self.engine == other.engine): - return Command(self.engine, self.gate.get_merged(other.gate), - self.qubits, self.control_qubits, - deepcopy(self.tags)) + if self.tags == other.tags and self.all_qubits == other.all_qubits and self.engine == other.engine: + return Command( + self.engine, + self.gate.get_merged(other.gate), + self.qubits, + self.control_qubits, + deepcopy(self.tags), + ) raise projectq.ops.NotMergeable("Commands not mergeable.") def _order_qubits(self, qubits): @@ -186,8 +198,7 @@ def _order_qubits(self, qubits): # e.g. [[0,4],[1,2,3]] interchangeable_qubit_indices = self.interchangeable_qubit_indices for old_positions in interchangeable_qubit_indices: - new_positions = sorted(old_positions, - key=lambda x: ordered_qubits[x][0].id) + new_positions = sorted(old_positions, key=lambda x: ordered_qubits[x][0].id) qubits_new_order = [ordered_qubits[i] for i in new_positions] for i in range(len(old_positions)): ordered_qubits[old_positions[i]] = qubits_new_order[i] @@ -210,7 +221,7 @@ def interchangeable_qubit_indices(self): @property def control_qubits(self): - """ Returns Qureg of control qubits.""" + """Returns Qureg of control qubits.""" return self._control_qubits @control_qubits.setter @@ -221,9 +232,7 @@ def control_qubits(self, qubits): Args: control_qubits (Qureg): quantum register """ - self._control_qubits = ([ - WeakQubitRef(qubit.engine, qubit.id) for qubit in qubits - ]) + self._control_qubits = [WeakQubitRef(qubit.engine, qubit.id) for qubit in qubits] self._control_qubits = sorted(self._control_qubits, key=lambda x: x.id) def add_control_qubits(self, qubits): @@ -239,9 +248,8 @@ def add_control_qubits(self, qubits): gate, i.e., the gate is only executed if all qubits are in state 1. """ - assert (isinstance(qubits, list)) - self._control_qubits.extend( - [WeakQubitRef(qubit.engine, qubit.id) for qubit in qubits]) + assert isinstance(qubits, list) + self._control_qubits.extend([WeakQubitRef(qubit.engine, qubit.id) for qubit in qubits]) self._control_qubits = sorted(self._control_qubits, key=lambda x: x.id) @property @@ -253,7 +261,7 @@ def all_qubits(self): WeakQubitRef objects) containing the control qubits and T[1:] contains the quantum registers to which the gate is applied. """ - return (self.control_qubits, ) + self.qubits + return (self.control_qubits,) + self.qubits @property def engine(self): @@ -288,9 +296,13 @@ def __eq__(self, other): Returns: True if Command objects are equal (same gate, applied to same qubits; ordered modulo interchangeability; and same tags) """ - if (isinstance(other, self.__class__) and self.gate == other.gate - and self.tags == other.tags and self.engine == other.engine - and self.all_qubits == other.all_qubits): + if ( + isinstance(other, self.__class__) + and self.gate == other.gate + and self.tags == other.tags + and self.engine == other.engine + and self.all_qubits == other.all_qubits + ): return True return False @@ -307,7 +319,7 @@ def to_string(self, symbols=False): qubits = self.qubits ctrlqubits = self.control_qubits if len(ctrlqubits) > 0: - qubits = (self.control_qubits, ) + qubits + qubits = (self.control_qubits,) + qubits qstring = "" if len(qubits) == 1: qstring = str(Qureg(qubits[0])) diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index b0b4d54c8..d4823df66 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# Copyright 2017, 2021 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. @@ -12,7 +12,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.ops._command.""" from copy import deepcopy @@ -53,13 +52,11 @@ def test_command_init(main_engine): # Test that quregs are ordered if gate has interchangeable qubits: symmetric_gate = BasicGate() symmetric_gate.interchangeable_qubit_indices = [[0, 1]] - symmetric_cmd = _command.Command(main_engine, symmetric_gate, - (qureg2, qureg1, qureg0)) + symmetric_cmd = _command.Command(main_engine, symmetric_gate, (qureg2, qureg1, qureg0)) assert cmd.gate == gate assert cmd.tags == [] expected_ordered_tuple = (qureg1, qureg2, qureg0) - for cmd_qureg, expected_qureg in zip(symmetric_cmd.qubits, - expected_ordered_tuple): + for cmd_qureg, expected_qureg in zip(symmetric_cmd.qubits, expected_ordered_tuple): assert cmd_qureg[0].id == expected_qureg[0].id assert symmetric_cmd._engine == main_engine @@ -123,6 +120,7 @@ def test_command_get_merged(main_engine): expected_cmd = _command.Command(main_engine, Rx(1.0), (qubit,)) expected_cmd.add_control_qubits(ctrl_qubit) expected_cmd.tags = ["TestTag"] + assert merged_cmd == expected_cmd # Don't merge commands as different control qubits cmd3 = _command.Command(main_engine, Rx(0.5), (qubit,)) cmd3.tags = ["TestTag"] @@ -138,7 +136,7 @@ def test_command_get_merged(main_engine): 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,)) + cmd = _command.Command(main_engine, Rx(0.0), (qubit,)) cmd2 = _command.Command(main_engine, Rx(0.5), (qubit2,)) inverse_cmd = cmd.get_inverse() inverse_cmd2 = cmd2.get_inverse() @@ -175,8 +173,14 @@ def test_command_interchangeable_qubit_indices(main_engine): qubit5 = Qureg([Qubit(main_engine, 5)]) input_tuple = (qubit4, qubit5, qubit3, qubit2, qubit1, qubit0) cmd = _command.Command(main_engine, gate, input_tuple) - assert (cmd.interchangeable_qubit_indices == [[0, 4, 5], [1, 2]] or - cmd.interchangeable_qubit_indices == [[1, 2], [0, 4, 5]]) + assert ( + cmd.interchangeable_qubit_indices + == [ + [0, 4, 5], + [1, 2], + ] + or cmd.interchangeable_qubit_indices == [[1, 2], [0, 4, 5]] + ) def test_commmand_add_control_qubits(main_engine): @@ -210,6 +214,10 @@ def test_command_engine(main_engine): assert id(cmd.control_qubits[0].engine) == id(main_engine) assert id(cmd.qubits[0][0].engine) == id(main_engine) + # Avoid raising exception upon Qubit destructions + qubit0[0].id = -1 + qubit1[0].id = -1 + def test_command_comparison(main_engine): qubit = Qureg([Qubit(main_engine, 0)]) @@ -244,13 +252,13 @@ def test_command_comparison(main_engine): assert cmd6 != cmd1 -def test_command_str(): +def test_command_str(main_engine): 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 * math.pi), (qubit,)) cmd.tags = ["TestTag"] cmd.add_control_qubits(ctrl_qubit) - cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) + cmd2 = _command.Command(main_engine, Rx(0.5 * math.pi), (qubit,)) if sys.version_info.major == 3: assert cmd.to_string(symbols=False) == "CRx(1.570796326795) | ( Qureg[1], Qureg[0] )" assert str(cmd2) == "Rx(1.570796326795) | Qureg[0]" @@ -259,13 +267,13 @@ def test_command_str(): assert str(cmd2) == "Rx(1.5707963268) | Qureg[0]" -def test_command_to_string(): +def test_command_to_string(main_engine): 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 * math.pi), (qubit,)) cmd.tags = ["TestTag"] cmd.add_control_qubits(ctrl_qubit) - cmd2 = _command.Command(main_engine, Rx(0.5*math.pi), (qubit,)) + cmd2 = _command.Command(main_engine, Rx(0.5 * math.pi), (qubit,)) assert cmd.to_string(symbols=True) == u"CRx(0.5π) | ( Qureg[1], Qureg[0] )" assert cmd2.to_string(symbols=True) == u"Rx(0.5π) | Qureg[0]" @@ -275,4 +283,3 @@ def test_command_to_string(): else: assert cmd.to_string(symbols=False) == "CRx(1.5707963268) | ( Qureg[1], Qureg[0] )" assert cmd2.to_string(symbols=False) == "Rx(1.5707963268) | Qureg[0]" - diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index be2240d00..44300f854 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Contains definitions of standard gates such as * Hadamard (H) @@ -48,32 +48,35 @@ import numpy as np from projectq.ops import get_inverse -from ._basics import (BasicGate, - MatrixGate, - SelfInverseGate, - BasicRotationGate, - BasicPhaseGate, - ClassicalInstructionGate, - FastForwardingGate, - BasicMathGate) +from ._basics import ( + BasicGate, + SelfInverseGate, + BasicRotationGate, + BasicPhaseGate, + ClassicalInstructionGate, + FastForwardingGate, +) from ._command import apply_command class HGate(SelfInverseGate): - """ Hadamard gate class """ + """Hadamard gate class""" + def __str__(self): return "H" @property def matrix(self): - return 1. / cmath.sqrt(2.) * np.matrix([[1, 1], [1, -1]]) + return 1.0 / cmath.sqrt(2.0) * np.matrix([[1, 1], [1, -1]]) + #: Shortcut (instance of) :class:`projectq.ops.HGate` H = HGate() class XGate(SelfInverseGate): - """ Pauli-X gate class """ + """Pauli-X gate class""" + def __str__(self): return "X" @@ -81,12 +84,14 @@ def __str__(self): def matrix(self): return np.matrix([[0, 1], [1, 0]]) + #: Shortcut (instance of) :class:`projectq.ops.XGate` X = NOT = XGate() class YGate(SelfInverseGate): - """ Pauli-Y gate class """ + """Pauli-Y gate class""" + def __str__(self): return "Y" @@ -94,12 +99,14 @@ def __str__(self): def matrix(self): return np.matrix([[0, -1j], [1j, 0]]) + #: Shortcut (instance of) :class:`projectq.ops.YGate` Y = YGate() class ZGate(SelfInverseGate): - """ Pauli-Z gate class """ + """Pauli-Z gate class""" + def __str__(self): return "Z" @@ -107,12 +114,14 @@ def __str__(self): def matrix(self): return np.matrix([[1, 0], [0, -1]]) + #: Shortcut (instance of) :class:`projectq.ops.ZGate` Z = ZGate() class SGate(BasicGate): - """ S gate class """ + """S gate class""" + @property def matrix(self): return np.matrix([[1, 0], [0, 1j]]) @@ -120,6 +129,7 @@ def matrix(self): def __str__(self): return "S" + #: Shortcut (instance of) :class:`projectq.ops.SGate` S = SGate() #: Inverse (and shortcut) of :class:`projectq.ops.SGate` @@ -127,7 +137,8 @@ def __str__(self): class TGate(BasicGate): - """ T gate class """ + """T gate class""" + @property def matrix(self): return np.matrix([[1, 0], [0, cmath.exp(1j * cmath.pi / 4)]]) @@ -135,6 +146,7 @@ def matrix(self): def __str__(self): return "T" + #: Shortcut (instance of) :class:`projectq.ops.TGate` T = TGate() #: Inverse (and shortcut) of :class:`projectq.ops.TGate` @@ -142,10 +154,11 @@ def __str__(self): class SqrtXGate(BasicGate): - """ Square-root X gate class """ + """Square-root X gate class""" + @property def matrix(self): - return 0.5 * np.matrix([[1+1j, 1-1j], [1-1j, 1+1j]]) + return 0.5 * np.matrix([[1 + 1j, 1 - 1j], [1 - 1j, 1 + 1j]]) def tex_str(self): return r'$\sqrt{X}$' @@ -153,12 +166,14 @@ def tex_str(self): def __str__(self): return "SqrtX" + #: Shortcut (instance of) :class:`projectq.ops.SqrtXGate` SqrtX = SqrtXGate() class SwapGate(SelfInverseGate): - """ Swap gate class (swaps 2 qubits) """ + """Swap gate class (swaps 2 qubits)""" + def __init__(self): SelfInverseGate.__init__(self) self.interchangeable_qubit_indices = [[0, 1]] @@ -168,17 +183,21 @@ def __str__(self): @property def matrix(self): + # fmt: off return np.matrix([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]]) + # fmt: on + #: Shortcut (instance of) :class:`projectq.ops.SwapGate` Swap = SwapGate() class SqrtSwapGate(BasicGate): - """ Square-root Swap gate class """ + """Square-root Swap gate class""" + def __init__(self): BasicGate.__init__(self) self.interchangeable_qubit_indices = [[0, 1]] @@ -188,10 +207,15 @@ def __str__(self): @property def matrix(self): - return np.matrix([[1, 0, 0, 0], - [0, 0.5+0.5j, 0.5-0.5j, 0], - [0, 0.5-0.5j, 0.5+0.5j, 0], - [0, 0, 0, 1]]) + return np.matrix( + [ + [1, 0, 0, 0], + [0, 0.5 + 0.5j, 0.5 - 0.5j, 0], + [0, 0.5 - 0.5j, 0.5 + 0.5j, 0], + [0, 0, 0, 1], + ] + ) + #: Shortcut (instance of) :class:`projectq.ops.SqrtSwapGate` SqrtSwap = SqrtSwapGate() @@ -202,81 +226,110 @@ class EntangleGate(BasicGate): Entangle gate (Hadamard on first qubit, followed by CNOTs applied to all other qubits). """ + def __str__(self): return "Entangle" + #: Shortcut (instance of) :class:`projectq.ops.EntangleGate` Entangle = EntangleGate() class Ph(BasicPhaseGate): - """ Phase gate (global phase) """ + """Phase gate (global phase)""" + @property def matrix(self): - return np.matrix([[cmath.exp(1j * self.angle), 0], - [0, cmath.exp(1j * self.angle)]]) + return np.matrix([[cmath.exp(1j * self.angle), 0], [0, cmath.exp(1j * self.angle)]]) class Rx(BasicRotationGate): - """ RotationX gate class """ + """RotationX gate class""" + @property def matrix(self): - return np.matrix([[math.cos(0.5 * self.angle), - -1j * math.sin(0.5 * self.angle)], - [-1j * math.sin(0.5 * self.angle), - math.cos(0.5 * self.angle)]]) + return np.matrix( + [ + [math.cos(0.5 * self.angle), -1j * math.sin(0.5 * self.angle)], + [-1j * math.sin(0.5 * self.angle), math.cos(0.5 * self.angle)], + ] + ) class Ry(BasicRotationGate): - """ RotationY gate class """ + """RotationY gate class""" + @property def matrix(self): - return np.matrix([[math.cos(0.5 * self.angle), - -math.sin(0.5 * self.angle)], - [math.sin(0.5 * self.angle), - math.cos(0.5 * self.angle)]]) + return np.matrix( + [ + [math.cos(0.5 * self.angle), -math.sin(0.5 * self.angle)], + [math.sin(0.5 * self.angle), math.cos(0.5 * self.angle)], + ] + ) class Rz(BasicRotationGate): - """ RotationZ gate class """ + """RotationZ gate class""" + @property def matrix(self): - return np.matrix([[cmath.exp(-.5 * 1j * self.angle), 0], - [0, cmath.exp(.5 * 1j * self.angle)]]) + return np.matrix( + [ + [cmath.exp(-0.5 * 1j * self.angle), 0], + [0, cmath.exp(0.5 * 1j * self.angle)], + ] + ) class Rxx(BasicRotationGate): - """ RotationXX gate class """ + """RotationXX gate class""" + @property def matrix(self): - return np.matrix([[cmath.cos(.5 * self.angle), 0, 0, -1j*cmath.sin(.5 * self.angle)], - [0, cmath.cos( .5 * self.angle), -1j*cmath.sin(.5 * self.angle), 0], - [0, -1j*cmath.sin(.5 * self.angle), cmath.cos( .5 * self.angle), 0], - [-1j*cmath.sin(.5 * self.angle), 0, 0, cmath.cos( .5 * self.angle)]]) + return np.matrix( + [ + [cmath.cos(0.5 * self.angle), 0, 0, -1j * cmath.sin(0.5 * self.angle)], + [0, cmath.cos(0.5 * self.angle), -1j * cmath.sin(0.5 * self.angle), 0], + [0, -1j * cmath.sin(0.5 * self.angle), cmath.cos(0.5 * self.angle), 0], + [-1j * cmath.sin(0.5 * self.angle), 0, 0, cmath.cos(0.5 * self.angle)], + ] + ) class Ryy(BasicRotationGate): - """ RotationYY gate class """ + """RotationYY gate class""" + @property def matrix(self): - return np.matrix([[cmath.cos(.5 * self.angle), 0, 0, 1j*cmath.sin(.5 * self.angle)], - [0, cmath.cos( .5 * self.angle), -1j*cmath.sin(.5 * self.angle), 0], - [0, -1j*cmath.sin(.5 * self.angle), cmath.cos( .5 * self.angle), 0], - [1j*cmath.sin(.5 * self.angle), 0, 0, cmath.cos( .5 * self.angle)]]) + return np.matrix( + [ + [cmath.cos(0.5 * self.angle), 0, 0, 1j * cmath.sin(0.5 * self.angle)], + [0, cmath.cos(0.5 * self.angle), -1j * cmath.sin(0.5 * self.angle), 0], + [0, -1j * cmath.sin(0.5 * self.angle), cmath.cos(0.5 * self.angle), 0], + [1j * cmath.sin(0.5 * self.angle), 0, 0, cmath.cos(0.5 * self.angle)], + ] + ) class Rzz(BasicRotationGate): - """ RotationZZ gate class """ + """RotationZZ gate class""" + @property def matrix(self): - return np.matrix([[cmath.exp(-.5 * 1j * self.angle), 0, 0, 0], - [0, cmath.exp( .5 * 1j * self.angle), 0, 0], - [0, 0, cmath.exp( .5 * 1j * self.angle), 0], - [0, 0, 0, cmath.exp(-.5 * 1j * self.angle)]]) + return np.matrix( + [ + [cmath.exp(-0.5 * 1j * self.angle), 0, 0, 0], + [0, cmath.exp(0.5 * 1j * self.angle), 0, 0], + [0, 0, cmath.exp(0.5 * 1j * self.angle), 0], + [0, 0, 0, cmath.exp(-0.5 * 1j * self.angle)], + ] + ) class R(BasicPhaseGate): - """ Phase-shift gate (equivalent to Rz up to a global phase) """ + """Phase-shift gate (equivalent to Rz up to a global phase)""" + @property def matrix(self): return np.matrix([[1, 0], [0, cmath.exp(1j * self.angle)]]) @@ -306,7 +359,8 @@ def __str__(self): class MeasureGate(FastForwardingGate): - """ Measurement gate class (for single qubits).""" + """Measurement gate class (for single qubits).""" + def __str__(self): return "Measure" @@ -324,65 +378,77 @@ def __or__(self, qubits): cmd = self.generate_command(([qubit],)) apply_command(cmd) if num_qubits > 1: - warnings.warn("Pending syntax change in future versions of " - "ProjectQ: \n Measure will be a single qubit gate " - "only. Use `All(Measure) | qureg` instead to " - "measure multiple qubits.") + warnings.warn( + "Pending syntax change in future versions of " + "ProjectQ: \n Measure will be a single qubit gate " + "only. Use `All(Measure) | qureg` instead to " + "measure multiple qubits." + ) + #: Shortcut (instance of) :class:`projectq.ops.MeasureGate` Measure = MeasureGate() class AllocateQubitGate(ClassicalInstructionGate): - """ Qubit allocation gate class """ + """Qubit allocation gate class""" + def __str__(self): return "Allocate" def get_inverse(self): return DeallocateQubitGate() + #: Shortcut (instance of) :class:`projectq.ops.AllocateQubitGate` Allocate = AllocateQubitGate() class DeallocateQubitGate(FastForwardingGate): - """ Qubit deallocation gate class """ + """Qubit deallocation gate class""" + def __str__(self): return "Deallocate" def get_inverse(self): return Allocate + #: Shortcut (instance of) :class:`projectq.ops.DeallocateQubitGate` Deallocate = DeallocateQubitGate() class AllocateDirtyQubitGate(ClassicalInstructionGate): - """ Dirty qubit allocation gate class """ + """Dirty qubit allocation gate class""" + def __str__(self): return "AllocateDirty" def get_inverse(self): return Deallocate + #: Shortcut (instance of) :class:`projectq.ops.AllocateDirtyQubitGate` AllocateDirty = AllocateDirtyQubitGate() class BarrierGate(BasicGate): - """ Barrier gate class """ + """Barrier gate class""" + def __str__(self): return "Barrier" def get_inverse(self): return Barrier + #: Shortcut (instance of) :class:`projectq.ops.BarrierGate` Barrier = BarrierGate() class FlipBits(SelfInverseGate): - """ Gate for flipping qubits by means of XGates """ + """Gate for flipping qubits by means of XGates""" + def __init__(self, bits_to_flip): """ Initialize FlipBits gate. @@ -412,14 +478,16 @@ def __init__(self, bits_to_flip): self.bits_to_flip = (self.bits_to_flip << 1) | bit def __str__(self): - return "FlipBits("+str(self.bits_to_flip)+")" + return "FlipBits(" + str(self.bits_to_flip) + ")" def __or__(self, qubits): quregs_tuple = self.make_tuple_of_qureg(qubits) if len(quregs_tuple) > 1: - raise ValueError(self.__str__()+' can only be applied to qubits,' - 'quregs, arrays of qubits, and tuples with one' - 'individual qubit') + raise ValueError( + self.__str__() + ' can only be applied to qubits,' + 'quregs, arrays of qubits, and tuples with one' + 'individual qubit' + ) for qureg in quregs_tuple: for i, qubit in enumerate(qureg): if (self.bits_to_flip >> i) & 1: diff --git a/projectq/ops/_gates_test.py b/projectq/ops/_gates_test.py index 88efa3a19..fb5977769 100755 --- a/projectq/ops/_gates_test.py +++ b/projectq/ops/_gates_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.ops._gates.""" import math @@ -20,9 +20,12 @@ import pytest from projectq import MainEngine -from projectq.ops import (All, FlipBits, get_inverse, SelfInverseGate, - BasicRotationGate, ClassicalInstructionGate, - FastForwardingGate, BasicGate, Measure) +from projectq.ops import ( + All, + FlipBits, + get_inverse, + Measure, +) from projectq.ops import _gates @@ -31,8 +34,7 @@ def test_h_gate(): gate = _gates.HGate() assert gate == gate.get_inverse() assert str(gate) == "H" - assert np.array_equal(gate.matrix, - 1. / math.sqrt(2) * np.matrix([[1, 1], [1, -1]])) + assert np.array_equal(gate.matrix, 1.0 / math.sqrt(2) * np.matrix([[1, 1], [1, -1]])) assert isinstance(_gates.H, _gates.HGate) @@ -73,9 +75,7 @@ def test_s_gate(): def test_t_gate(): gate = _gates.TGate() assert str(gate) == "T" - assert np.array_equal(gate.matrix, - np.matrix([[1, 0], - [0, cmath.exp(1j * cmath.pi / 4)]])) + assert np.array_equal(gate.matrix, np.matrix([[1, 0], [0, cmath.exp(1j * cmath.pi / 4)]])) assert isinstance(_gates.T, _gates.TGate) assert isinstance(_gates.Tdag, type(get_inverse(gate))) assert isinstance(_gates.Tdagger, type(get_inverse(gate))) @@ -84,10 +84,8 @@ def test_t_gate(): def test_sqrtx_gate(): gate = _gates.SqrtXGate() assert str(gate) == "SqrtX" - assert np.array_equal(gate.matrix, np.matrix([[0.5 + 0.5j, 0.5 - 0.5j], - [0.5 - 0.5j, 0.5 + 0.5j]])) - assert np.array_equal(gate.matrix * gate.matrix, - np.matrix([[0j, 1], [1, 0]])) + assert np.array_equal(gate.matrix, np.matrix([[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]])) + assert np.array_equal(gate.matrix * gate.matrix, np.matrix([[0j, 1], [1, 0]])) assert isinstance(_gates.SqrtX, _gates.SqrtXGate) @@ -96,9 +94,7 @@ def test_swap_gate(): assert gate == gate.get_inverse() assert str(gate) == "Swap" assert gate.interchangeable_qubit_indices == [[0, 1]] - assert np.array_equal(gate.matrix, - np.matrix([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], - [0, 0, 0, 1]])) + assert np.array_equal(gate.matrix, np.matrix([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]])) assert isinstance(_gates.Swap, _gates.SwapGate) @@ -106,13 +102,18 @@ def test_sqrtswap_gate(): sqrt_gate = _gates.SqrtSwapGate() swap_gate = _gates.SwapGate() assert str(sqrt_gate) == "SqrtSwap" - assert np.array_equal(sqrt_gate.matrix * sqrt_gate.matrix, - swap_gate.matrix) - assert np.array_equal(sqrt_gate.matrix, - np.matrix([[1, 0, 0, 0], - [0, 0.5 + 0.5j, 0.5 - 0.5j, 0], - [0, 0.5 - 0.5j, 0.5 + 0.5j, 0], - [0, 0, 0, 1]])) + assert np.array_equal(sqrt_gate.matrix * sqrt_gate.matrix, swap_gate.matrix) + assert np.array_equal( + sqrt_gate.matrix, + np.matrix( + [ + [1, 0, 0, 0], + [0, 0.5 + 0.5j, 0.5 - 0.5j, 0], + [0, 0.5 - 0.5j, 0.5 + 0.5j, 0], + [0, 0, 0, 1], + ] + ), + ) assert isinstance(_gates.SqrtSwap, _gates.SqrtSwapGate) @@ -122,72 +123,81 @@ def test_engangle_gate(): assert isinstance(_gates.Entangle, _gates.EntangleGate) -@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) def test_rx(angle): gate = _gates.Rx(angle) - expected_matrix = np.matrix([[math.cos(0.5 * angle), - -1j * math.sin(0.5 * angle)], - [-1j * math.sin(0.5 * angle), - math.cos(0.5 * angle)]]) + expected_matrix = np.matrix( + [ + [math.cos(0.5 * angle), -1j * math.sin(0.5 * angle)], + [-1j * math.sin(0.5 * angle), math.cos(0.5 * angle)], + ] + ) assert gate.matrix.shape == expected_matrix.shape assert np.allclose(gate.matrix, expected_matrix) -@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) def test_ry(angle): gate = _gates.Ry(angle) - expected_matrix = np.matrix([[math.cos(0.5 * angle), - -math.sin(0.5 * angle)], - [math.sin(0.5 * angle), - math.cos(0.5 * angle)]]) + expected_matrix = np.matrix( + [ + [math.cos(0.5 * angle), -math.sin(0.5 * angle)], + [math.sin(0.5 * angle), math.cos(0.5 * angle)], + ] + ) assert gate.matrix.shape == expected_matrix.shape assert np.allclose(gate.matrix, expected_matrix) -@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) def test_rz(angle): gate = _gates.Rz(angle) - expected_matrix = np.matrix([[cmath.exp(-.5 * 1j * angle), 0], - [0, cmath.exp(.5 * 1j * angle)]]) + expected_matrix = np.matrix([[cmath.exp(-0.5 * 1j * angle), 0], [0, cmath.exp(0.5 * 1j * angle)]]) assert gate.matrix.shape == expected_matrix.shape assert np.allclose(gate.matrix, expected_matrix) -@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) def test_rxx(angle): gate = _gates.Rxx(angle) - expected_matrix = np.matrix([[cmath.cos(.5 * angle), 0, 0, -1j * cmath.sin(.5 * angle)], - [0, cmath.cos(.5 * angle), -1j * cmath.sin(.5 * angle), 0], - [0, -1j * cmath.sin(.5 * angle), cmath.cos(.5 * angle), 0], - [-1j * cmath.sin(.5 * angle), 0, 0, cmath.cos(.5 * angle)]]) + expected_matrix = np.matrix( + [ + [cmath.cos(0.5 * angle), 0, 0, -1j * cmath.sin(0.5 * angle)], + [0, cmath.cos(0.5 * angle), -1j * cmath.sin(0.5 * angle), 0], + [0, -1j * cmath.sin(0.5 * angle), cmath.cos(0.5 * angle), 0], + [-1j * cmath.sin(0.5 * angle), 0, 0, cmath.cos(0.5 * angle)], + ] + ) assert gate.matrix.shape == expected_matrix.shape assert np.allclose(gate.matrix, expected_matrix) -@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) def test_ryy(angle): gate = _gates.Ryy(angle) - expected_matrix = np.matrix([[cmath.cos(.5 * angle), 0, 0, 1j * cmath.sin(.5 * angle)], - [0, cmath.cos(.5 * angle), -1j * cmath.sin(.5 * angle), 0], - [0, -1j * cmath.sin(.5 * angle), cmath.cos(.5 * angle), 0], - [ 1j * cmath.sin(.5 * angle), 0, 0, cmath.cos(.5 * angle)]]) + expected_matrix = np.matrix( + [ + [cmath.cos(0.5 * angle), 0, 0, 1j * cmath.sin(0.5 * angle)], + [0, cmath.cos(0.5 * angle), -1j * cmath.sin(0.5 * angle), 0], + [0, -1j * cmath.sin(0.5 * angle), cmath.cos(0.5 * angle), 0], + [1j * cmath.sin(0.5 * angle), 0, 0, cmath.cos(0.5 * angle)], + ] + ) assert gate.matrix.shape == expected_matrix.shape assert np.allclose(gate.matrix, expected_matrix) -@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, - 4 * math.pi]) +@pytest.mark.parametrize("angle", [0, 0.2, 2.1, 4.1, 2 * math.pi, 4 * math.pi]) def test_rzz(angle): gate = _gates.Rzz(angle) - expected_matrix = np.matrix([[cmath.exp(-.5 * 1j * angle), 0, 0, 0], - [0, cmath.exp( .5 * 1j * angle), 0, 0], - [0, 0, cmath.exp( .5 * 1j * angle), 0], - [0, 0, 0, cmath.exp(-.5 * 1j * angle)]]) + expected_matrix = np.matrix( + [ + [cmath.exp(-0.5 * 1j * angle), 0, 0, 0], + [0, cmath.exp(0.5 * 1j * angle), 0, 0], + [0, 0, cmath.exp(0.5 * 1j * angle), 0], + [0, 0, 0, cmath.exp(-0.5 * 1j * angle)], + ] + ) assert gate.matrix.shape == expected_matrix.shape assert np.allclose(gate.matrix, expected_matrix) @@ -196,8 +206,7 @@ def test_rzz(angle): def test_ph(angle): gate = _gates.Ph(angle) gate2 = _gates.Ph(angle + 2 * math.pi) - expected_matrix = np.matrix([[cmath.exp(1j * angle), 0], - [0, cmath.exp(1j * angle)]]) + expected_matrix = np.matrix([[cmath.exp(1j * angle), 0], [0, cmath.exp(1j * angle)]]) assert gate.matrix.shape == expected_matrix.shape assert np.allclose(gate.matrix, expected_matrix) assert gate2.matrix.shape == expected_matrix.shape @@ -298,7 +307,7 @@ def test_simulator_flip_bits(bits_to_flip, result): qubits = eng.allocate_qureg(4) FlipBits(bits_to_flip) | qubits eng.flush() - assert pytest.approx(eng.backend.get_probability(result, qubits)) == 1. + assert pytest.approx(eng.backend.get_probability(result, qubits)) == 1.0 All(Measure) | qubits @@ -306,26 +315,26 @@ def test_flip_bits_can_be_applied_to_various_qubit_qureg_formats(): eng = MainEngine() qubits = eng.allocate_qureg(4) eng.flush() - assert pytest.approx(eng.backend.get_probability('0000', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability('0000', qubits)) == 1.0 FlipBits([0, 1, 1, 0]) | qubits eng.flush() - assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1.0 FlipBits([1]) | qubits[0] eng.flush() - assert pytest.approx(eng.backend.get_probability('1110', qubits)) == 1. - FlipBits([1]) | (qubits[0], ) + assert pytest.approx(eng.backend.get_probability('1110', qubits)) == 1.0 + FlipBits([1]) | (qubits[0],) eng.flush() - assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1.0 FlipBits([1, 1]) | [qubits[0], qubits[1]] eng.flush() - assert pytest.approx(eng.backend.get_probability('1010', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability('1010', qubits)) == 1.0 FlipBits(-1) | qubits eng.flush() - assert pytest.approx(eng.backend.get_probability('0101', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability('0101', qubits)) == 1.0 FlipBits(-4) | [qubits[0], qubits[1], qubits[2], qubits[3]] eng.flush() - assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability('0110', qubits)) == 1.0 FlipBits(2) | [qubits[0]] + [qubits[1], qubits[2]] eng.flush() - assert pytest.approx(eng.backend.get_probability('0010', qubits)) == 1. + assert pytest.approx(eng.backend.get_probability('0010', qubits)) == 1.0 All(Measure) | qubits diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index cca5e7412..6f38648ba 100644 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,31 +12,30 @@ # 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 meta gates, i.e., * DaggeredGate (Represents the inverse of an arbitrary gate) * ControlledGate (Represents a controlled version of an arbitrary gate) * Tensor/All (Applies a single qubit gate to all supplied qubits), e.g., - Example: + Example: .. code-block:: python Tensor(H) | (qubit1, qubit2) # apply H to qubit #1 and #2 As well as the meta functions * get_inverse (Tries to access the get_inverse member function of a gate - and upon failure returns a DaggeredGate) + and upon failure returns a DaggeredGate) * C (Creates an n-ary controlled version of an arbitrary gate) """ from ._basics import BasicGate, NotInvertible -from ._command import Command, apply_command class ControlQubitError(Exception): """ Exception thrown when wrong number of control qubits are supplied. """ + pass @@ -132,6 +132,7 @@ def get_inverse(gate): except NotInvertible: return DaggeredGate(gate) + def is_identity(gate): """ Return True if the gate is an identity gate. @@ -149,6 +150,7 @@ def is_identity(gate): """ return gate.is_identity() + class ControlledGate(BasicGate): """ Controlled version of a gate. @@ -193,7 +195,7 @@ def __init__(self, gate, n=1): self._n = n def __str__(self): - """ Return string representation, i.e., CC...C(gate). """ + """Return string representation, i.e., CC...C(gate).""" return "C" * self._n + str(self._gate) def get_inverse(self): @@ -230,18 +232,20 @@ def __or__(self, qubits): # Test that there were enough control quregs and that that # the last control qubit was the last qubit in a qureg. if len(ctrl) != self._n: - raise ControlQubitError("Wrong number of control qubits. " - "First qureg(s) need to contain exactly " - "the required number of control quregs.") + raise ControlQubitError( + "Wrong number of control qubits. " + "First qureg(s) need to contain exactly " + "the required number of control quregs." + ) import projectq.meta + with projectq.meta.Control(gate_quregs[0][0].engine, ctrl): self._gate | tuple(gate_quregs) def __eq__(self, other): - """ Compare two ControlledGate objects (return True if equal). """ - return (isinstance(other, self.__class__) and - self._gate == other._gate and self._n == other._n) + """Compare two ControlledGate objects (return True if equal).""" + return isinstance(other, self.__class__) and self._gate == other._gate and self._n == other._n def __ne__(self, other): return not self.__eq__(other) @@ -277,12 +281,12 @@ class Tensor(BasicGate): """ def __init__(self, gate): - """ Initialize a Tensor object for the gate. """ + """Initialize a Tensor object for the gate.""" BasicGate.__init__(self) self._gate = gate def __str__(self): - """ Return string representation. """ + """Return string representation.""" return "Tensor(" + str(self._gate) + ")" def get_inverse(self): @@ -299,7 +303,7 @@ def __ne__(self, other): return not self.__eq__(other) def __or__(self, qubits): - """ Applies the gate to every qubit in the quantum register qubits. """ + """Applies the gate to every qubit in the quantum register qubits.""" if isinstance(qubits, tuple): assert len(qubits) == 1 qubits = qubits[0] @@ -307,5 +311,6 @@ def __or__(self, qubits): for qubit in qubits: self._gate | qubit + #: Shortcut (instance of) :class:`projectq.ops.Tensor` All = Tensor diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py index 8632a99e5..52b9e00d1 100755 --- a/projectq/ops/_metagates_test.py +++ b/projectq/ops/_metagates_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.ops._gates.""" import cmath @@ -19,20 +19,28 @@ import numpy as np import pytest -from projectq.types import Qubit, Qureg +from projectq.types import Qubit from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import (T, Y, NotInvertible, Entangle, Rx, - FastForwardingGate, Command, C, - ClassicalInstructionGate, All) +from projectq.ops import ( + T, + Y, + NotInvertible, + Entangle, + Rx, + FastForwardingGate, + Command, + C, + ClassicalInstructionGate, + All, +) from projectq.ops import _metagates def test_tensored_controlled_gate(): saving_backend = DummyEngine(save_commands=True) - main_engine = MainEngine(backend=saving_backend, - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=saving_backend, engine_list=[DummyEngine()]) gate = Rx(0.6) qubit0 = Qubit(main_engine, 0) qubit1 = Qubit(main_engine, 1) @@ -55,9 +63,7 @@ def test_daggered_gate_init(): # Test init and matrix dagger_inv = _metagates.DaggeredGate(not_invertible_gate) assert dagger_inv._gate == not_invertible_gate - assert np.array_equal(dagger_inv.matrix, - np.matrix([[1, 0], - [0, cmath.exp(-1j * cmath.pi / 4)]])) + assert np.array_equal(dagger_inv.matrix, np.matrix([[1, 0], [0, cmath.exp(-1j * cmath.pi / 4)]])) inv = _metagates.DaggeredGate(invertible_gate) assert inv._gate == invertible_gate assert np.array_equal(inv.matrix, np.matrix([[0, -1j], [1j, 0]])) @@ -117,18 +123,18 @@ def test_get_inverse(): assert invertible_gate.get_inverse() == Y # Check get_inverse(gate) inv = _metagates.get_inverse(not_invertible_gate) - assert (isinstance(inv, _metagates.DaggeredGate) and - inv._gate == not_invertible_gate) + assert isinstance(inv, _metagates.DaggeredGate) and inv._gate == not_invertible_gate 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) + non_identity_gate = Rx(0.5) assert not non_identity_gate.is_identity() - assert not _metagates.is_identity(non_identity_gate) + assert not _metagates.is_identity(non_identity_gate) # Choose gate which is an identity gate: - identity_gate=Rx(0.) + identity_gate = Rx(0.0) assert identity_gate.is_identity() assert _metagates.is_identity(identity_gate) @@ -167,19 +173,16 @@ def test_controlled_gate_empty_controls(): def test_controlled_gate_or(): saving_backend = DummyEngine(save_commands=True) - main_engine = MainEngine(backend=saving_backend, - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=saving_backend, engine_list=[DummyEngine()]) gate = Rx(0.6) qubit0 = Qubit(main_engine, 0) qubit1 = Qubit(main_engine, 1) qubit2 = Qubit(main_engine, 2) qubit3 = Qubit(main_engine, 3) - expected_cmd = Command(main_engine, gate, ([qubit3],), - controls=[qubit0, qubit1, qubit2]) + expected_cmd = Command(main_engine, gate, ([qubit3],), controls=[qubit0, qubit1, qubit2]) received_commands = [] # Option 1: - _metagates.ControlledGate(gate, 3) | ([qubit1], [qubit0], - [qubit2], [qubit3]) + _metagates.ControlledGate(gate, 3) | ([qubit1], [qubit0], [qubit2], [qubit3]) # Option 2: _metagates.ControlledGate(gate, 3) | (qubit1, qubit0, qubit2, qubit3) # Option 3: @@ -191,8 +194,7 @@ def test_controlled_gate_or(): _metagates.ControlledGate(gate, 3) | (qubit1, [qubit0, qubit2, qubit3]) # Remove Allocate and Deallocate gates for cmd in saving_backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) assert len(received_commands) == 4 for cmd in received_commands: @@ -240,8 +242,7 @@ def test_tensor_comparison(): def test_tensor_or(): saving_backend = DummyEngine(save_commands=True) - main_engine = MainEngine(backend=saving_backend, - engine_list=[DummyEngine()]) + main_engine = MainEngine(backend=saving_backend, engine_list=[DummyEngine()]) gate = Rx(0.6) qubit0 = Qubit(main_engine, 0) qubit1 = Qubit(main_engine, 1) @@ -253,8 +254,7 @@ def test_tensor_or(): received_commands = [] # Remove Allocate and Deallocate gates for cmd in saving_backend.received_commands: - if not (isinstance(cmd.gate, FastForwardingGate) or - isinstance(cmd.gate, ClassicalInstructionGate)): + if not (isinstance(cmd.gate, FastForwardingGate) or isinstance(cmd.gate, ClassicalInstructionGate)): received_commands.append(cmd) # Check results assert len(received_commands) == 6 diff --git a/projectq/ops/_qaagate.py b/projectq/ops/_qaagate.py index 751b9dc60..a7a6c438a 100755 --- a/projectq/ops/_qaagate.py +++ b/projectq/ops/_qaagate.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -71,11 +72,11 @@ def func_oracle(eng,system_qubits,qaa_ancilla): "good" states """ + def __init__(self, algorithm, oracle): BasicGate.__init__(self) self.algorithm = algorithm self.oracle = oracle def __str__(self): - return 'QAA(Algorithm = {0}, Oracle = {1})'.format( - str(self.algorithm.__name__), str(self.oracle.__name__)) + return 'QAA(Algorithm = {0}, Oracle = {1})'.format(str(self.algorithm.__name__), str(self.oracle.__name__)) diff --git a/projectq/ops/_qaagate_test.py b/projectq/ops/_qaagate_test.py index 3e15e6801..ccc224938 100755 --- a/projectq/ops/_qaagate_test.py +++ b/projectq/ops/_qaagate_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,17 +12,17 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.ops._qaagate.""" from projectq.ops import _qaagate, All, H, X def test_qaa_str(): + def func_algorithm(): + All(H) - def func_algorithm(): All(H) - - def func_oracle(): All(X) + def func_oracle(): + All(X) gate = _qaagate.QAA(func_algorithm, func_oracle) assert str(gate) == "QAA(Algorithm = func_algorithm, Oracle = func_oracle)" diff --git a/projectq/ops/_qftgate.py b/projectq/ops/_qftgate.py index 7c1e3984b..8b22bcc87 100755 --- a/projectq/ops/_qftgate.py +++ b/projectq/ops/_qftgate.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +20,7 @@ class QFTGate(BasicGate): """ Quantum Fourier Transform gate. """ + def __str__(self): return "QFT" diff --git a/projectq/ops/_qftgate_test.py b/projectq/ops/_qftgate_test.py index 4382d632b..8fa43058f 100755 --- a/projectq/ops/_qftgate_test.py +++ b/projectq/ops/_qftgate_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.ops._qftgate.""" from projectq.ops import _qftgate diff --git a/projectq/ops/_qpegate.py b/projectq/ops/_qpegate.py index 08beee743..43834d55f 100755 --- a/projectq/ops/_qpegate.py +++ b/projectq/ops/_qpegate.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +22,7 @@ class QPE(BasicGate): See setups.decompositions for the complete implementation """ + def __init__(self, unitary): BasicGate.__init__(self) self.unitary = unitary diff --git a/projectq/ops/_qpegate_test.py b/projectq/ops/_qpegate_test.py index 5ffcbf185..2b19cd4d0 100755 --- a/projectq/ops/_qpegate_test.py +++ b/projectq/ops/_qpegate_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.ops._qpegate.""" from projectq.ops import _qpegate, X diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index ece98d698..621d50d77 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,39 +12,35 @@ # 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. - """QubitOperator stores a sum of Pauli operators acting on qubits.""" import cmath import copy -import itertools - -import numpy from ._basics import BasicGate, NotInvertible, NotMergeable from ._command import apply_command from ._gates import Ph, X, Y, Z - EQ_TOLERANCE = 1e-12 - # Define products of all Pauli operators for symbolic multiplication. -_PAULI_OPERATOR_PRODUCTS = {('I', 'I'): (1., 'I'), - ('I', 'X'): (1., 'X'), - ('X', 'I'): (1., 'X'), - ('I', 'Y'): (1., 'Y'), - ('Y', 'I'): (1., 'Y'), - ('I', 'Z'): (1., 'Z'), - ('Z', 'I'): (1., 'Z'), - ('X', 'X'): (1., 'I'), - ('Y', 'Y'): (1., 'I'), - ('Z', 'Z'): (1., 'I'), - ('X', 'Y'): (1.j, 'Z'), - ('X', 'Z'): (-1.j, 'Y'), - ('Y', 'X'): (-1.j, 'Z'), - ('Y', 'Z'): (1.j, 'X'), - ('Z', 'X'): (1.j, 'Y'), - ('Z', 'Y'): (-1.j, 'X')} +_PAULI_OPERATOR_PRODUCTS = { + ('I', 'I'): (1.0, 'I'), + ('I', 'X'): (1.0, 'X'), + ('X', 'I'): (1.0, 'X'), + ('I', 'Y'): (1.0, 'Y'), + ('Y', 'I'): (1.0, 'Y'), + ('I', 'Z'): (1.0, 'Z'), + ('Z', 'I'): (1.0, 'Z'), + ('X', 'X'): (1.0, 'I'), + ('Y', 'Y'): (1.0, 'I'), + ('Z', 'Z'): (1.0, 'I'), + ('X', 'Y'): (1.0j, 'Z'), + ('X', 'Z'): (-1.0j, 'Y'), + ('Y', 'X'): (-1.0j, 'Z'), + ('Y', 'Z'): (1.0j, 'X'), + ('Z', 'X'): (1.0j, 'Y'), + ('Z', 'Y'): (-1.0j, 'X'), +} class QubitOperatorError(Exception): @@ -108,7 +105,7 @@ class QubitOperator(BasicGate): **value**: Coefficient of this term as a (complex) float """ - def __init__(self, term=None, coefficient=1.): + def __init__(self, term=None, coefficient=1.0): """ Inits a QubitOperator. @@ -164,18 +161,15 @@ def __init__(self, term=None, coefficient=1.): else: # Test that input is a tuple of tuples and correct action for local_operator in term: - if (not isinstance(local_operator, tuple) or - len(local_operator) != 2): + if not isinstance(local_operator, tuple) or len(local_operator) != 2: raise ValueError("term specified incorrectly.") qubit_num, action = local_operator if not isinstance(action, str) or action not in 'XYZ': - raise ValueError("Invalid action provided: must be " - "string 'X', 'Y', or 'Z'.") + raise ValueError("Invalid action provided: must be string 'X', 'Y', or 'Z'.") if not (isinstance(qubit_num, int) and qubit_num >= 0): - raise QubitOperatorError("Invalid qubit number " - "provided to QubitTerm: " - "must be a non-negative " - "int.") + raise QubitOperatorError( + "Invalid qubit number provided to QubitTerm: must be a non-negative int." + ) # Sort and add to self.terms: term = list(term) term.sort(key=lambda loc_operator: loc_operator[0]) @@ -190,13 +184,9 @@ def __init__(self, term=None, coefficient=1.): for local_operator in list_ops: qubit_num, action = local_operator if not isinstance(action, str) or action not in 'XYZ': - raise ValueError("Invalid action provided: must be " - "string 'X', 'Y', or 'Z'.") + raise ValueError("Invalid action provided: must be string 'X', 'Y', or 'Z'.") if not (isinstance(qubit_num, int) and qubit_num >= 0): - raise QubitOperatorError("Invalid qubit number " - "provided to QubitTerm: " - "must be a non-negative " - "int.") + raise QubitOperatorError("Invalid qubit number provided to QubitTerm: must be a non-negative int.") # Sort and add to self.terms: list_ops.sort(key=lambda loc_operator: loc_operator[0]) self.terms[tuple(list_ops)] = coefficient @@ -240,7 +230,7 @@ def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12): a = self.terms[term] b = other.terms[term] # math.isclose does this in Python >=3.5 - if not abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol): + if not abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol): return False # terms only in one (compare to 0.0 so only abs_tol) for term in set(self.terms).symmetric_difference(set(other.terms)): @@ -304,19 +294,22 @@ def __or__(self, qubits): raise TypeError("Only one qubit or qureg allowed.") # Check that operator is unitary if not len(self.terms) == 1: - raise TypeError("Too many terms. Only QubitOperators consisting " - "of a single term (single n-qubit Pauli operator) " - "with a coefficient of unit length can be applied " - "to qubits with this function.") - (term, coefficient), = self.terms.items() + raise TypeError( + "Too many terms. Only QubitOperators consisting " + "of a single term (single n-qubit Pauli operator) " + "with a coefficient of unit length can be applied " + "to qubits with this function." + ) + ((term, coefficient),) = self.terms.items() phase = cmath.phase(coefficient) - if (abs(coefficient) < 1 - EQ_TOLERANCE or - abs(coefficient) > 1 + EQ_TOLERANCE): - raise TypeError("abs(coefficient) != 1. Only QubitOperators " - "consisting of a single term (single n-qubit " - "Pauli operator) with a coefficient of unit " - "length can be applied to qubits with this " - "function.") + if abs(coefficient) < 1 - EQ_TOLERANCE or abs(coefficient) > 1 + EQ_TOLERANCE: + raise TypeError( + "abs(coefficient) != 1. Only QubitOperators " + "consisting of a single term (single n-qubit " + "Pauli operator) with a coefficient of unit " + "length can be applied to qubits with this " + "function." + ) # Test if we need to apply only Ph if term == (): Ph(phase) | qubits[0][0] @@ -327,8 +320,7 @@ def __or__(self, qubits): for index, action in term: non_trivial_qubits.add(index) if max(non_trivial_qubits) >= num_qubits: - raise ValueError("QubitOperator acts on more qubits than the gate " - "is applied to.") + raise ValueError("QubitOperator acts on more qubits than the gate is applied to.") # Apply X, Y, Z, if QubitOperator acts only on one qubit if len(term) == 1: if term[0][1] == "X": @@ -347,8 +339,7 @@ def __or__(self, qubits): new_index[non_trivial_qubits[i]] = i new_qubitoperator = QubitOperator() assert len(new_qubitoperator.terms) == 0 - new_term = tuple([(new_index[index], action) - for index, action in term]) + new_term = tuple([(new_index[index], action) for index, action in term]) new_qubitoperator.terms[new_term] = coefficient new_qubits = [qubits[0][i] for i in non_trivial_qubits] # Apply new gate @@ -366,10 +357,9 @@ def get_inverse(self): """ if len(self.terms) == 1: - (term, coefficient), = self.terms.items() - if (not abs(coefficient) < 1 - EQ_TOLERANCE and not - abs(coefficient) > 1 + EQ_TOLERANCE): - return QubitOperator(term, coefficient**(-1)) + ((term, coefficient),) = self.terms.items() + if not abs(coefficient) < 1 - EQ_TOLERANCE and not abs(coefficient) > 1 + EQ_TOLERANCE: + return QubitOperator(term, coefficient ** (-1)) raise NotInvertible("BasicGate: No get_inverse() implemented.") def get_merged(self, other): @@ -381,9 +371,7 @@ def get_merged(self, other): Raises: NotMergeable: merging is not possible """ - if (isinstance(other, self.__class__) and - len(other.terms) == 1 and - len(self.terms) == 1): + if isinstance(other, self.__class__) and len(other.terms) == 1 and len(self.terms) == 1: return self * other else: raise NotMergeable() @@ -406,8 +394,7 @@ def __imul__(self, multiplier): result_terms = dict() for left_term in self.terms: for right_term in multiplier.terms: - new_coefficient = (self.terms[left_term] * - multiplier.terms[right_term]) + new_coefficient = self.terms[left_term] * multiplier.terms[right_term] # Loop through local operators and create new sorted list # of representing the product local operator: @@ -416,19 +403,15 @@ def __imul__(self, multiplier): right_operator_index = 0 n_operators_left = len(left_term) n_operators_right = len(right_term) - while (left_operator_index < n_operators_left and - right_operator_index < n_operators_right): - (left_qubit, left_loc_op) = ( - left_term[left_operator_index]) - (right_qubit, right_loc_op) = ( - right_term[right_operator_index]) + while left_operator_index < n_operators_left and right_operator_index < n_operators_right: + (left_qubit, left_loc_op) = left_term[left_operator_index] + (right_qubit, right_loc_op) = right_term[right_operator_index] # Multiply local operators acting on the same qubit if left_qubit == right_qubit: left_operator_index += 1 right_operator_index += 1 - (scalar, loc_op) = _PAULI_OPERATOR_PRODUCTS[ - (left_loc_op, right_loc_op)] + (scalar, loc_op) = _PAULI_OPERATOR_PRODUCTS[(left_loc_op, right_loc_op)] # Add new term. if loc_op != 'I': @@ -447,8 +430,7 @@ def __imul__(self, multiplier): # Finish the remainding operators: if left_operator_index == n_operators_left: - product_operators += right_term[ - right_operator_index::] + product_operators += right_term[right_operator_index::] elif right_operator_index == n_operators_right: product_operators += left_term[left_operator_index::] @@ -461,8 +443,7 @@ def __imul__(self, multiplier): self.terms = result_terms return self else: - raise TypeError('Cannot in-place multiply term of invalid type ' + - 'to QubitTerm.') + raise TypeError('Cannot in-place multiply term of invalid type ' + 'to QubitTerm.') def __mul__(self, multiplier): """ @@ -477,14 +458,12 @@ def __mul__(self, multiplier): Raises: TypeError: Invalid type cannot be multiply with QubitOperator. """ - if (isinstance(multiplier, (int, float, complex)) or - isinstance(multiplier, QubitOperator)): + if isinstance(multiplier, (int, float, complex)) or isinstance(multiplier, QubitOperator): product = copy.deepcopy(self) product *= multiplier return product else: - raise TypeError( - 'Object of invalid type cannot multiply with QubitOperator.') + raise TypeError('Object of invalid type cannot multiply with QubitOperator.') def __rmul__(self, multiplier): """ @@ -504,8 +483,7 @@ def __rmul__(self, multiplier): TypeError: Object of invalid type cannot multiply QubitOperator. """ if not isinstance(multiplier, (int, float, complex)): - raise TypeError( - 'Object of invalid type cannot multiply with QubitOperator.') + raise TypeError('Object of invalid type cannot multiply with QubitOperator.') return self * multiplier def __truediv__(self, divisor): @@ -528,20 +506,12 @@ def __truediv__(self, divisor): raise TypeError('Cannot divide QubitOperator by non-scalar type.') return self * (1.0 / divisor) - def __div__(self, divisor): - """ For compatibility with Python 2. """ - return self.__truediv__(divisor) - def __itruediv__(self, divisor): if not isinstance(divisor, (int, float, complex)): raise TypeError('Cannot divide QubitOperator by non-scalar type.') - self *= (1.0 / divisor) + self *= 1.0 / divisor return self - def __idiv__(self, divisor): - """ For compatibility with Python 2. """ - return self.__itruediv__(divisor) - def __iadd__(self, addend): """ In-place method for += addition of QubitOperator. @@ -555,7 +525,7 @@ def __iadd__(self, addend): if isinstance(addend, QubitOperator): for term in addend.terms: if term in self.terms: - if abs(addend.terms[term] + self.terms[term]) > 0.: + if abs(addend.terms[term] + self.terms[term]) > 0.0: self.terms[term] += addend.terms[term] else: self.terms.pop(term) @@ -566,7 +536,7 @@ def __iadd__(self, addend): return self def __add__(self, addend): - """ Return self + addend for a QubitOperator. """ + """Return self + addend for a QubitOperator.""" summand = copy.deepcopy(self) summand += addend return summand @@ -584,7 +554,7 @@ def __isub__(self, subtrahend): if isinstance(subtrahend, QubitOperator): for term in subtrahend.terms: if term in self.terms: - if abs(self.terms[term] - subtrahend.terms[term]) > 0.: + if abs(self.terms[term] - subtrahend.terms[term]) > 0.0: self.terms[term] -= subtrahend.terms[term] else: self.terms.pop(term) @@ -595,13 +565,13 @@ def __isub__(self, subtrahend): return self def __sub__(self, subtrahend): - """ Return self - subtrahend for a QubitOperator. """ + """Return self - subtrahend for a QubitOperator.""" minuend = copy.deepcopy(self) minuend -= subtrahend return minuend def __neg__(self): - return -1. * self + return -1.0 * self def __str__(self): """Return an easy-to-read string representation.""" diff --git a/projectq/ops/_qubit_operator_test.py b/projectq/ops/_qubit_operator_test.py index 76c832bf8..8077a8583 100644 --- a/projectq/ops/_qubit_operator_test.py +++ b/projectq/ops/_qubit_operator_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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 _qubit_operator.py.""" import cmath import copy @@ -29,22 +29,24 @@ def test_pauli_operator_product_unchanged(): - correct = {('I', 'I'): (1., 'I'), - ('I', 'X'): (1., 'X'), - ('X', 'I'): (1., 'X'), - ('I', 'Y'): (1., 'Y'), - ('Y', 'I'): (1., 'Y'), - ('I', 'Z'): (1., 'Z'), - ('Z', 'I'): (1., 'Z'), - ('X', 'X'): (1., 'I'), - ('Y', 'Y'): (1., 'I'), - ('Z', 'Z'): (1., 'I'), - ('X', 'Y'): (1.j, 'Z'), - ('X', 'Z'): (-1.j, 'Y'), - ('Y', 'X'): (-1.j, 'Z'), - ('Y', 'Z'): (1.j, 'X'), - ('Z', 'X'): (1.j, 'Y'), - ('Z', 'Y'): (-1.j, 'X')} + correct = { + ('I', 'I'): (1.0, 'I'), + ('I', 'X'): (1.0, 'X'), + ('X', 'I'): (1.0, 'X'), + ('I', 'Y'): (1.0, 'Y'), + ('Y', 'I'): (1.0, 'Y'), + ('I', 'Z'): (1.0, 'Z'), + ('Z', 'I'): (1.0, 'Z'), + ('X', 'X'): (1.0, 'I'), + ('Y', 'Y'): (1.0, 'I'), + ('Z', 'Z'): (1.0, 'I'), + ('X', 'Y'): (1.0j, 'Z'), + ('X', 'Z'): (-1.0j, 'Y'), + ('Y', 'X'): (-1.0j, 'Z'), + ('Y', 'Z'): (1.0j, 'X'), + ('Z', 'X'): (1.0j, 'Y'), + ('Z', 'Y'): (-1.0j, 'X'), + } assert qo._PAULI_OPERATOR_PRODUCTS == correct @@ -53,8 +55,7 @@ def test_init_defaults(): assert len(loc_op.terms) == 0 -@pytest.mark.parametrize("coefficient", [0.5, 0.6j, numpy.float64(2.303), - numpy.complex128(-1j)]) +@pytest.mark.parametrize("coefficient", [0.5, 0.6j, numpy.float64(2.303), numpy.complex128(-1j)]) def test_init_tuple(coefficient): loc_op = ((0, 'X'), (5, 'Y'), (6, 'Z')) qubit_op = qo.QubitOperator(loc_op, coefficient) @@ -63,61 +64,61 @@ def test_init_tuple(coefficient): def test_init_str(): - qubit_op = qo.QubitOperator('X0 Y5 Z12', -1.) + qubit_op = qo.QubitOperator('X0 Y5 Z12', -1.0) correct = ((0, 'X'), (5, 'Y'), (12, 'Z')) assert correct in qubit_op.terms assert qubit_op.terms[correct] == -1.0 def test_init_str_identity(): - qubit_op = qo.QubitOperator('', 2.) + qubit_op = qo.QubitOperator('', 2.0) assert len(qubit_op.terms) == 1 assert () in qubit_op.terms - assert qubit_op.terms[()] == pytest.approx(2.) + assert qubit_op.terms[()] == pytest.approx(2.0) def test_init_bad_term(): with pytest.raises(ValueError): - qubit_op = qo.QubitOperator(list()) + qo.QubitOperator(list()) def test_init_bad_coefficient(): with pytest.raises(ValueError): - qubit_op = qo.QubitOperator('X0', "0.5") + qo.QubitOperator('X0', "0.5") def test_init_bad_action(): with pytest.raises(ValueError): - qubit_op = qo.QubitOperator('Q0') + qo.QubitOperator('Q0') def test_init_bad_action_in_tuple(): with pytest.raises(ValueError): - qubit_op = qo.QubitOperator(((1, 'Q'),)) + qo.QubitOperator(((1, 'Q'),)) def test_init_bad_qubit_num_in_tuple(): with pytest.raises(qo.QubitOperatorError): - qubit_op = qo.QubitOperator((("1", 'X'),)) + qo.QubitOperator((("1", 'X'),)) def test_init_bad_tuple(): with pytest.raises(ValueError): - qubit_op = qo.QubitOperator(((0, 1, 'X'),)) + qo.QubitOperator(((0, 1, 'X'),)) def test_init_bad_str(): with pytest.raises(ValueError): - qubit_op = qo.QubitOperator('X') + qo.QubitOperator('X') def test_init_bad_qubit_num(): with pytest.raises(qo.QubitOperatorError): - qubit_op = qo.QubitOperator('X-1') + qo.QubitOperator('X-1') def test_isclose_abs_tol(): - a = qo.QubitOperator('X0', -1.) + a = qo.QubitOperator('X0', -1.0) b = qo.QubitOperator('X0', -1.05) c = qo.QubitOperator('X0', -1.11) assert a.isclose(b, rel_tol=1e-14, abs_tol=0.1) @@ -130,30 +131,30 @@ def test_isclose_abs_tol(): def test_compress(): - a = qo.QubitOperator('X0', .9e-12) + a = qo.QubitOperator('X0', 0.9e-12) assert len(a.terms) == 1 a.compress() assert len(a.terms) == 0 - a = qo.QubitOperator('X0', 1. + 1j) - a.compress(.5) + a = qo.QubitOperator('X0', 1.0 + 1j) + a.compress(0.5) assert len(a.terms) == 1 for term in a.terms: - assert a.terms[term] == 1. + 1j + assert a.terms[term] == 1.0 + 1j a = qo.QubitOperator('X0', 1.1 + 1j) - a.compress(1.) + a.compress(1.0) assert len(a.terms) == 1 for term in a.terms: assert a.terms[term] == 1.1 - a = qo.QubitOperator('X0', 1.1 + 1j) + qo.QubitOperator('X1', 1.e-6j) + a = qo.QubitOperator('X0', 1.1 + 1j) + qo.QubitOperator('X1', 1.0e-6j) a.compress() assert len(a.terms) == 2 for term in a.terms: assert isinstance(a.terms[term], complex) - a.compress(1.e-5) + a.compress(1.0e-5) assert len(a.terms) == 1 for term in a.terms: assert isinstance(a.terms[term], complex) - a.compress(1.) + a.compress(1.0) assert len(a.terms) == 1 for term in a.terms: assert isinstance(a.terms[term], float) @@ -194,8 +195,7 @@ def test_isclose_different_num_terms(): def test_get_inverse(): qo0 = qo.QubitOperator("X1 Z2", cmath.exp(0.6j)) qo1 = qo.QubitOperator("", 1j) - assert qo0.get_inverse().isclose( - qo.QubitOperator("X1 Z2", cmath.exp(-0.6j))) + assert qo0.get_inverse().isclose(qo.QubitOperator("X1 Z2", cmath.exp(-0.6j))) assert qo1.get_inverse().isclose(qo.QubitOperator("", -1j)) qo0 += qo1 with pytest.raises(NotInvertible): @@ -205,7 +205,6 @@ def test_get_inverse(): def test_get_merged(): qo0 = qo.QubitOperator("X1 Z2", 1j) qo1 = qo.QubitOperator("Y3", 1j) - merged = qo0.get_merged(qo1) assert qo0.isclose(qo.QubitOperator("X1 Z2", 1j)) assert qo1.isclose(qo.QubitOperator("Y3", 1j)) assert qo0.get_merged(qo1).isclose(qo.QubitOperator("X1 Z2 Y3", -1)) @@ -235,7 +234,7 @@ def test_or_one_qubit(): eng.flush() z | qureg eng.flush() - assert saving_backend.received_commands[4].gate == Ph(math.pi/2.) + assert saving_backend.received_commands[4].gate == Ph(math.pi / 2.0) assert saving_backend.received_commands[6].gate == X assert saving_backend.received_commands[6].qubits == ([qureg[1]],) @@ -277,8 +276,7 @@ def test_rescaling_of_indices(): op = qo.QubitOperator("X0 Y1 Z3", 1j) op | qureg eng.flush() - assert saving_backend.received_commands[5].gate.isclose( - qo.QubitOperator("X0 Y1 Z2", 1j)) + assert saving_backend.received_commands[5].gate.isclose(qo.QubitOperator("X0 Y1 Z2", 1j)) # test that gate creates a new QubitOperator assert op.isclose(qo.QubitOperator("X0 Y1 Z3", 1j)) @@ -286,12 +284,11 @@ def test_rescaling_of_indices(): def test_imul_inplace(): qubit_op = qo.QubitOperator("X1") prev_id = id(qubit_op) - qubit_op *= 3. + qubit_op *= 3.0 assert id(qubit_op) == prev_id -@pytest.mark.parametrize("multiplier", [0.5, 0.6j, numpy.float64(2.303), - numpy.complex128(-1j)]) +@pytest.mark.parametrize("multiplier", [0.5, 0.6j, numpy.float64(2.303), numpy.complex128(-1j)]) def test_imul_scalar(multiplier): loc_op = ((1, 'X'), (2, 'Y')) qubit_op = qo.QubitOperator(loc_op) @@ -300,13 +297,14 @@ def test_imul_scalar(multiplier): def test_imul_qubit_op(): - op1 = qo.QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.j) + op1 = qo.QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.0j) op2 = qo.QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) op1 *= op2 - correct_coefficient = 1.j * 3.0j * 0.5 + correct_coefficient = 1.0j * 3.0j * 0.5 correct_term = ((0, 'Y'), (1, 'X'), (3, 'Z'), (11, 'X')) assert len(op1.terms) == 1 assert correct_term in op1.terms + assert op1.terms[correct_term] == correct_coefficient def test_imul_qubit_op_2(): @@ -348,13 +346,12 @@ def test_mul_bad_multiplier(): def test_mul_out_of_place(): - op1 = qo.QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.j) + op1 = qo.QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.0j) op2 = qo.QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) op3 = op1 * op2 - correct_coefficient = 1.j * 3.0j * 0.5 + correct_coefficient = 1.0j * 3.0j * 0.5 correct_term = ((0, 'Y'), (1, 'X'), (3, 'Z'), (11, 'X')) - assert op1.isclose(qo.QubitOperator( - ((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.j)) + assert op1.isclose(qo.QubitOperator(((0, 'Y'), (3, 'X'), (8, 'Z'), (11, 'X')), 3.0j)) assert op2.isclose(qo.QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5)) assert op3.isclose(qo.QubitOperator(correct_term, correct_coefficient)) @@ -370,14 +367,12 @@ def test_mul_multiple_terms(): op += qo.QubitOperator(((1, 'Z'), (3, 'X'), (8, 'Z')), 1.2) op += qo.QubitOperator(((1, 'Z'), (3, 'Y'), (9, 'Z')), 1.4j) res = op * op - correct = qo.QubitOperator((), 0.5**2 + 1.2**2 + 1.4j**2) - correct += qo.QubitOperator(((1, 'Y'), (3, 'Z')), - 2j * 1j * 0.5 * 1.2) + correct = qo.QubitOperator((), 0.5 ** 2 + 1.2 ** 2 + 1.4j ** 2) + correct += qo.QubitOperator(((1, 'Y'), (3, 'Z')), 2j * 1j * 0.5 * 1.2) assert res.isclose(correct) -@pytest.mark.parametrize("multiplier", [0.5, 0.6j, numpy.float64(2.303), - numpy.complex128(-1j)]) +@pytest.mark.parametrize("multiplier", [0.5, 0.6j, numpy.float64(2.303), numpy.complex128(-1j)]) def test_rmul_scalar(multiplier): op = qo.QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) res1 = op * multiplier @@ -391,20 +386,15 @@ def test_rmul_bad_multiplier(): op = "0.5" * op -@pytest.mark.parametrize("divisor", [0.5, 0.6j, numpy.float64(2.303), - numpy.complex128(-1j), 2]) +@pytest.mark.parametrize("divisor", [0.5, 0.6j, numpy.float64(2.303), numpy.complex128(-1j), 2]) def test_truediv_and_div(divisor): op = qo.QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) - op2 = copy.deepcopy(op) original = copy.deepcopy(op) res = op / divisor - res2 = op2.__div__(divisor) # To test python 2 version as well - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) assert res.isclose(correct) - assert res2.isclose(correct) # Test if done out of place assert op.isclose(original) - assert op2.isclose(original) def test_truediv_bad_divisor(): @@ -413,20 +403,15 @@ def test_truediv_bad_divisor(): op = op / "0.5" -@pytest.mark.parametrize("divisor", [0.5, 0.6j, numpy.float64(2.303), - numpy.complex128(-1j), 2]) +@pytest.mark.parametrize("divisor", [0.5, 0.6j, numpy.float64(2.303), numpy.complex128(-1j), 2]) def test_itruediv_and_idiv(divisor): op = qo.QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) - op2 = copy.deepcopy(op) original = copy.deepcopy(op) - correct = op * (1. / divisor) + correct = op * (1.0 / divisor) op /= divisor - op2.__idiv__(divisor) # To test python 2 version as well assert op.isclose(correct) - assert op2.isclose(correct) # Test if done in-place assert not op.isclose(original) - assert not op2.isclose(original) def test_itruediv_bad_divisor(): @@ -556,8 +541,7 @@ def test_str_empty(): def test_str_multiple_terms(): op = qo.QubitOperator(((1, 'X'), (3, 'Y'), (8, 'Z')), 0.5) op += qo.QubitOperator(((1, 'Y'), (3, 'Y'), (8, 'Z')), 0.6) - assert (str(op) == "0.5 X1 Y3 Z8 +\n0.6 Y1 Y3 Z8" or - str(op) == "0.6 Y1 Y3 Z8 +\n0.5 X1 Y3 Z8") + assert str(op) == "0.5 X1 Y3 Z8 +\n0.6 Y1 Y3 Z8" or str(op) == "0.6 Y1 Y3 Z8 +\n0.5 X1 Y3 Z8" op2 = qo.QubitOperator((), 2) assert str(op2) == "2 I" diff --git a/projectq/ops/_shortcuts.py b/projectq/ops/_shortcuts.py index 0635ca9f3..0e06586cb 100755 --- a/projectq/ops/_shortcuts.py +++ b/projectq/ops/_shortcuts.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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 few shortcuts for certain gates such as * CNOT = C(NOT) @@ -32,8 +32,6 @@ def CRz(angle): CNOT = CX = C(NOT) - CZ = C(Z) - Toffoli = C(CNOT) diff --git a/projectq/ops/_shortcuts_test.py b/projectq/ops/_shortcuts_test.py index d6bd8b707..dd8a65d71 100755 --- a/projectq/ops/_shortcuts_test.py +++ b/projectq/ops/_shortcuts_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.ops._shortcuts.""" from projectq.ops import ControlledGate, Rz diff --git a/projectq/ops/_state_prep.py b/projectq/ops/_state_prep.py index 4ef51879d..af6dce015 100644 --- a/projectq/ops/_state_prep.py +++ b/projectq/ops/_state_prep.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +20,7 @@ class StatePreparation(BasicGate): """ Gate for transforming qubits in state |0> to any desired quantum state. """ + def __init__(self, final_state): """ Initialize StatePreparation gate. diff --git a/projectq/ops/_state_prep_test.py b/projectq/ops/_state_prep_test.py index 161bd53a1..5826d8e56 100644 --- a/projectq/ops/_state_prep_test.py +++ b/projectq/ops/_state_prep_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,11 +12,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.ops._state_prep.""" -import projectq - from projectq.ops import _state_prep, X diff --git a/projectq/ops/_time_evolution.py b/projectq/ops/_time_evolution.py index 46e8979b6..d80d7388b 100644 --- a/projectq/ops/_time_evolution.py +++ b/projectq/ops/_time_evolution.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,6 +48,7 @@ class TimeEvolution(BasicGate): hamiltonian(QubitOperator): hamiltonaian H """ + def __init__(self, time, hamiltonian): """ Initialize time evolution gate. @@ -75,12 +77,9 @@ def __init__(self, time, hamiltonian): self.hamiltonian = copy.deepcopy(hamiltonian) for term in hamiltonian.terms: if self.hamiltonian.terms[term].imag == 0: - self.hamiltonian.terms[term] = float( - self.hamiltonian.terms[term].real) + self.hamiltonian.terms[term] = float(self.hamiltonian.terms[term].real) else: - raise NotHermitianOperatorError("hamiltonian must be " - "hermitian and hence only " - "have real coefficients.") + raise NotHermitianOperatorError("hamiltonian must be hermitian and hence only have real coefficients.") def get_inverse(self): """ @@ -120,18 +119,14 @@ def get_merged(self, other): New TimeEvolution gate equivalent to the two merged gates. """ rel_tol = 1e-9 - if (isinstance(other, TimeEvolution) and - set(self.hamiltonian.terms) == set(other.hamiltonian.terms)): + if isinstance(other, TimeEvolution) and set(self.hamiltonian.terms) == set(other.hamiltonian.terms): factor = None for term in self.hamiltonian.terms: if factor is None: - factor = (self.hamiltonian.terms[term] / - float(other.hamiltonian.terms[term])) + factor = self.hamiltonian.terms[term] / float(other.hamiltonian.terms[term]) else: - tmp = (self.hamiltonian.terms[term] / - float(other.hamiltonian.terms[term])) - if not abs(factor - tmp) <= ( - rel_tol * max(abs(factor), abs(tmp))): + tmp = self.hamiltonian.terms[term] / float(other.hamiltonian.terms[term]) + if not abs(factor - tmp) <= (rel_tol * max(abs(factor), abs(tmp))): raise NotMergeable("Cannot merge these two gates.") # Terms are proportional to each other new_time = self.time + other.time / factor @@ -193,8 +188,7 @@ def __or__(self, qubits): for index, action in term: non_trivial_qubits.add(index) if max(non_trivial_qubits) >= num_qubits: - raise ValueError("hamiltonian acts on more qubits than the gate " - "is applied to.") + raise ValueError("hamiltonian acts on more qubits than the gate is applied to.") # create new TimeEvolution gate with rescaled qubit indices in # self.hamiltonian which are ordered from # 0,...,len(non_trivial_qubits) - 1 @@ -205,8 +199,7 @@ def __or__(self, qubits): new_hamiltonian = QubitOperator() assert len(new_hamiltonian.terms) == 0 for term in self.hamiltonian.terms: - new_term = tuple([(new_index[index], action) - for index, action in term]) + new_term = tuple([(new_index[index], action) for index, action in term]) new_hamiltonian.terms[new_term] = self.hamiltonian.terms[term] new_gate = TimeEvolution(time=self.time, hamiltonian=new_hamiltonian) new_qubits = [qubits[0][i] for i in non_trivial_qubits] @@ -215,11 +208,11 @@ def __or__(self, qubits): apply_command(cmd) def __eq__(self, other): - """ Not implemented as this object is a floating point type.""" + """Not implemented as this object is a floating point type.""" return NotImplemented def __ne__(self, other): - """ Not implemented as this object is a floating point type.""" + """Not implemented as this object is a floating point type.""" return NotImplemented def __str__(self): diff --git a/projectq/ops/_time_evolution_test.py b/projectq/ops/_time_evolution_test.py index cbad44fa2..e4ee41a79 100644 --- a/projectq/ops/_time_evolution_test.py +++ b/projectq/ops/_time_evolution_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.ops._time_evolution.""" import cmath import copy @@ -54,22 +54,22 @@ def test_init_makes_copy(): def test_init_bad_time(): hamiltonian = QubitOperator("Z2", 0.5) with pytest.raises(TypeError): - gate = te.TimeEvolution(1.5j, hamiltonian) + te.TimeEvolution(1.5j, hamiltonian) def test_init_bad_hamiltonian(): with pytest.raises(TypeError): - gate = te.TimeEvolution(2, "something else") + te.TimeEvolution(2, "something else") def test_init_not_hermitian(): hamiltonian = QubitOperator("Z2", 1e-12j) with pytest.raises(te.NotHermitianOperatorError): - gate = te.TimeEvolution(1, hamiltonian) + te.TimeEvolution(1, hamiltonian) def test_init_cast_complex_to_float(): - hamiltonian = QubitOperator("Z2", 2+0j) + hamiltonian = QubitOperator("Z2", 2 + 0j) gate = te.TimeEvolution(1, hamiltonian) assert isinstance(gate.hamiltonian.terms[((2, 'Z'),)], float) pytest.approx(gate.hamiltonian.terms[((2, 'Z'),)]) == 2.0 @@ -122,10 +122,10 @@ def test_get_merged_not_close_enough(): hamiltonian += QubitOperator("X3", 1) gate = te.TimeEvolution(2, hamiltonian) hamiltonian2 = QubitOperator("Z2", 4) - hamiltonian2 += QubitOperator("X3", 2+1e-8) + hamiltonian2 += QubitOperator("X3", 2 + 1e-8) gate2 = te.TimeEvolution(5, hamiltonian2) with pytest.raises(NotMergeable): - merged = gate.get_merged(gate2) + gate.get_merged(gate2) def test_get_merged_bad_gate(): @@ -254,15 +254,14 @@ def test_or_gate_identity(): eng = MainEngine(backend=saving_backend, engine_list=[]) qureg = eng.allocate_qureg(4) hamiltonian = QubitOperator((), 3.4) - correct_h = copy.deepcopy(hamiltonian) + correct_h = copy.deepcopy(hamiltonian) # noqa: F841 gate = te.TimeEvolution(2.1, hamiltonian) gate | qureg eng.flush() cmd = saving_backend.received_commands[4] assert isinstance(cmd.gate, Ph) assert cmd.gate == Ph(-3.4 * 2.1) - correct = numpy.array([[cmath.exp(-1j * 3.4 * 2.1), 0], - [0, cmath.exp(-1j * 3.4 * 2.1)]]) + correct = numpy.array([[cmath.exp(-1j * 3.4 * 2.1), 0], [0, cmath.exp(-1j * 3.4 * 2.1)]]) print(correct) print(cmd.gate.matrix) assert numpy.allclose(cmd.gate.matrix, correct) @@ -284,5 +283,4 @@ def test_str(): hamiltonian = QubitOperator("X0 Z1") hamiltonian += QubitOperator("Y1", 0.5) gate = te.TimeEvolution(2.1, hamiltonian) - assert (str(gate) == "exp(-2.1j * (0.5 Y1 +\n1.0 X0 Z1))" or - str(gate) == "exp(-2.1j * (1.0 X0 Z1 +\n0.5 Y1))") + assert str(gate) == "exp(-2.1j * (0.5 Y1 +\n1.0 X0 Z1))" or str(gate) == "exp(-2.1j * (1.0 X0 Z1 +\n0.5 Y1))" diff --git a/projectq/ops/_uniformly_controlled_rotation.py b/projectq/ops/_uniformly_controlled_rotation.py index d3bef04fa..e8d8ccaee 100644 --- a/projectq/ops/_uniformly_controlled_rotation.py +++ b/projectq/ops/_uniformly_controlled_rotation.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,13 +46,14 @@ class UniformlyControlledRy(BasicGate): conditioned on the control qubits being in state k. """ + def __init__(self, angles): BasicGate.__init__(self) rounded_angles = [] for angle in angles: - new_angle = round(float(angle) % (4. * math.pi), ANGLE_PRECISION) + new_angle = round(float(angle) % (4.0 * math.pi), ANGLE_PRECISION) if new_angle > 4 * math.pi - ANGLE_TOLERANCE: - new_angle = 0. + new_angle = 0.0 rounded_angles.append(new_angle) self.angles = rounded_angles @@ -60,8 +62,7 @@ def get_inverse(self): def get_merged(self, other): if isinstance(other, self.__class__): - new_angles = [angle1 + angle2 for (angle1, angle2) in - zip(self.angles, other.angles)] + new_angles = [angle1 + angle2 for (angle1, angle2) in zip(self.angles, other.angles)] return self.__class__(new_angles) raise NotMergeable() @@ -69,7 +70,7 @@ def __str__(self): return "UniformlyControlledRy(" + str(self.angles) + ")" def __eq__(self, other): - """ Return True if same class, same rotation angles.""" + """Return True if same class, same rotation angles.""" if isinstance(other, self.__class__): return self.angles == other.angles else: @@ -110,13 +111,14 @@ class UniformlyControlledRz(BasicGate): conditioned on the control qubits being in state k. """ + def __init__(self, angles): BasicGate.__init__(self) rounded_angles = [] for angle in angles: - new_angle = round(float(angle) % (4. * math.pi), ANGLE_PRECISION) + new_angle = round(float(angle) % (4.0 * math.pi), ANGLE_PRECISION) if new_angle > 4 * math.pi - ANGLE_TOLERANCE: - new_angle = 0. + new_angle = 0.0 rounded_angles.append(new_angle) self.angles = rounded_angles @@ -125,8 +127,7 @@ def get_inverse(self): def get_merged(self, other): if isinstance(other, self.__class__): - new_angles = [angle1 + angle2 for (angle1, angle2) in - zip(self.angles, other.angles)] + new_angles = [angle1 + angle2 for (angle1, angle2) in zip(self.angles, other.angles)] return self.__class__(new_angles) raise NotMergeable() @@ -134,7 +135,7 @@ def __str__(self): return "UniformlyControlledRz(" + str(self.angles) + ")" def __eq__(self, other): - """ Return True if same class, same rotation angles.""" + """Return True if same class, same rotation angles.""" if isinstance(other, self.__class__): return self.angles == other.angles else: diff --git a/projectq/ops/_uniformly_controlled_rotation_test.py b/projectq/ops/_uniformly_controlled_rotation_test.py index f58b198f4..14014da04 100644 --- a/projectq/ops/_uniformly_controlled_rotation_test.py +++ b/projectq/ops/_uniformly_controlled_rotation_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,8 +12,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.ops._uniformly_controlled_rotation.""" import math @@ -24,23 +23,20 @@ from projectq.ops import _uniformly_controlled_rotation as ucr -@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, - ucr.UniformlyControlledRz]) +@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, ucr.UniformlyControlledRz]) def test_init_rounding(gate_class): gate = gate_class([0.1 + 4 * math.pi, -1e-14]) - assert gate.angles == [0.1, 0.] + assert gate.angles == [0.1, 0.0] -@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, - ucr.UniformlyControlledRz]) +@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, ucr.UniformlyControlledRz]) def test_get_inverse(gate_class): gate = gate_class([0.1, 0.2, 0.3, 0.4]) inverse = gate.get_inverse() assert inverse == gate_class([-0.1, -0.2, -0.3, -0.4]) -@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, - ucr.UniformlyControlledRz]) +@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, ucr.UniformlyControlledRz]) def test_get_merged(gate_class): gate1 = gate_class([0.1, 0.2, 0.3, 0.4]) gate2 = gate_class([0.1, 0.2, 0.3, 0.4]) @@ -59,8 +55,7 @@ def test_str_and_hash(): assert hash(gate2) == hash("UniformlyControlledRz([0.1, 0.2, 0.3, 0.4])") -@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, - ucr.UniformlyControlledRz]) +@pytest.mark.parametrize("gate_class", [ucr.UniformlyControlledRy, ucr.UniformlyControlledRz]) def test_equality(gate_class): gate1 = gate_class([0.1, 0.2]) gate2 = gate_class([0.1, 0.2 + 1e-14]) diff --git a/projectq/setups/__init__.py b/projectq/setups/__init__.py index ee1451dcd..16fc4afdf 100755 --- a/projectq/setups/__init__.py +++ b/projectq/setups/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/setups/aqt.py b/projectq/setups/aqt.py index 38f0b4591..78849dc98 100644 --- a/projectq/setups/aqt.py +++ b/projectq/setups/aqt.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,13 +23,10 @@ translated in the backend in the Rx/Ry/MS gate set. """ -import projectq -import projectq.setups.decompositions from projectq.setups import restrictedgateset -from projectq.ops import (Rx, Ry, Rxx, Barrier) -from projectq.cengines import (LocalOptimizer, IBM5QubitMapper, - SwapAndCNOTFlipper, BasicMapperEngine, - GridMapper) +from projectq.ops import Rx, Ry, Rxx, Barrier +from projectq.cengines import BasicMapperEngine + from projectq.backends._aqt._aqt_http_client import show_devices @@ -39,8 +37,7 @@ def get_engine_list(token=None, device=None): devices = show_devices(token) aqt_setup = [] if device not in devices: - raise DeviceOfflineError('Error when configuring engine list: device ' - 'requested for Backend not connected') + raise DeviceOfflineError('Error when configuring engine list: device requested for Backend not connected') if device == 'aqt_simulator': # The 11 qubit online simulator doesn't need a specific mapping for # gates. Can also run wider gateset but this setup keep the @@ -62,9 +59,7 @@ 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) - setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry), - two_qubit_gates=(Rxx,), - other_gates=(Barrier, )) + setup = restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry), two_qubit_gates=(Rxx,), other_gates=(Barrier,)) setup.extend(aqt_setup) return setup @@ -75,5 +70,3 @@ class DeviceOfflineError(Exception): class DeviceNotHandledError(Exception): pass - - diff --git a/projectq/setups/aqt_test.py b/projectq/setups/aqt_test.py index 341a0cc66..d95ce2f2e 100644 --- a/projectq/setups/aqt_test.py +++ b/projectq/setups/aqt_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2020 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,19 +21,25 @@ def test_aqt_mapper_in_cengines(monkeypatch): import projectq.setups.aqt 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 { - 'aqt_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 {'aqt_simulator': {'coupling_map': connections, 'version': '0.0.0', 'nq': 32}} monkeypatch.setattr(projectq.setups.aqt, "show_devices", mock_show_devices) - engines_simulator = projectq.setups.aqt.get_engine_list( - device='aqt_simulator') + engines_simulator = projectq.setups.aqt.get_engine_list(device='aqt_simulator') assert len(engines_simulator) == 13 @@ -40,15 +47,22 @@ def test_aqt_errors(monkeypatch): import projectq.setups.aqt 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 { - 'aqt_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 {'aqt_imaginary': {'coupling_map': connections, 'version': '0.0.0', 'nq': 6}} monkeypatch.setattr(projectq.setups.aqt, "show_devices", mock_show_devices) with pytest.raises(projectq.setups.aqt.DeviceOfflineError): diff --git a/projectq/setups/awsbraket.py b/projectq/setups/awsbraket.py index c9bd1b289..c5ec54a12 100644 --- a/projectq/setups/awsbraket.py +++ b/projectq/setups/awsbraket.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,14 +23,24 @@ that will be used in the backend. """ -import projectq -import projectq.setups.decompositions from projectq.setups import restrictedgateset -from projectq.ops import (R, Swap, H, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z, - SqrtX, Barrier) -from projectq.cengines import (LocalOptimizer, IBM5QubitMapper, - SwapAndCNOTFlipper, BasicMapperEngine, - GridMapper) +from projectq.ops import ( + R, + Swap, + H, + Rx, + Ry, + Rz, + S, + Sdag, + T, + Tdag, + X, + Y, + Z, + SqrtX, + Barrier, +) from projectq.backends._awsbraket._awsbraket_boto3_client import show_devices @@ -39,8 +50,7 @@ def get_engine_list(credentials=None, device=None): # gate, etc.. devices = show_devices(credentials) if device not in devices: - raise DeviceOfflineError('Error when configuring engine list: device ' - 'requested for Backend not available') + raise DeviceOfflineError('Error when configuring engine list: device requested for Backend not available') # We left the real device to manage the mapping and optimizacion: "The IonQ # and Rigetti devices compile the provided circuit into their respective @@ -52,22 +62,24 @@ def get_engine_list(credentials=None, device=None): if device == 'SV1': setup = restrictedgateset.get_engine_list( - one_qubit_gates=(R, H, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z, - SqrtX), - two_qubit_gates=(Swap, ), - other_gates=(Barrier, )) + one_qubit_gates=(R, H, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z, SqrtX), + two_qubit_gates=(Swap,), + other_gates=(Barrier,), + ) return setup if device == 'Aspen-8': setup = restrictedgateset.get_engine_list( one_qubit_gates=(R, H, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z), - two_qubit_gates=(Swap, ), - other_gates=(Barrier, )) + two_qubit_gates=(Swap,), + other_gates=(Barrier,), + ) return setup if device == 'IonQ Device': setup = restrictedgateset.get_engine_list( one_qubit_gates=(H, Rx, Ry, Rz, S, Sdag, T, Tdag, X, Y, Z, SqrtX), - two_qubit_gates=(Swap, ), - other_gates=(Barrier, )) + two_qubit_gates=(Swap,), + other_gates=(Barrier,), + ) return setup diff --git a/projectq/setups/awsbraket_test.py b/projectq/setups/awsbraket_test.py index ef9f50447..2f78d8bc2 100644 --- a/projectq/setups/awsbraket_test.py +++ b/projectq/setups/awsbraket_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2021 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,82 +15,101 @@ """Tests for projectq.setup.awsbraket.""" import pytest -from unittest.mock import MagicMock, Mock, patch -from botocore.response import StreamingBody -import botocore -from io import StringIO +from unittest.mock import patch import json -import projectq.setups.awsbraket +# ============================================================================== +_has_boto3 = True +try: + import projectq.setups.awsbraket -search_value = { - "devices": [ - { - "deviceArn": "arn1", - "deviceName": "SV1", - "deviceType": "SIMULATOR", - "deviceStatus": "ONLINE", - "providerName": "pname1", - }, - { - "deviceArn": "arn2", - "deviceName": "Aspen-8", - "deviceType": "QPU", - "deviceStatus": "OFFLINE", - "providerName": "pname1", - }, - { - "deviceArn": "arn3", - "deviceName": "IonQ Device", - "deviceType": "QPU", - "deviceStatus": "ONLINE", - "providerName": "pname2", - }, - ] - } +except ImportError: + _has_boto3 = False + +has_boto3 = pytest.mark.skipif(not _has_boto3, reason="boto3 package is not installed") +# ============================================================================== + +search_value = { + "devices": [ + { + "deviceArn": "arn1", + "deviceName": "SV1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn2", + "deviceName": "Aspen-8", + "deviceType": "QPU", + "deviceStatus": "OFFLINE", + "providerName": "pname1", + }, + { + "deviceArn": "arn3", + "deviceName": "IonQ Device", + "deviceType": "QPU", + "deviceStatus": "ONLINE", + "providerName": "pname2", + }, + ] +} device_value_devicecapabilities = json.dumps( { - "braketSchemaHeader": { - "name": "braket.device_schema.rigetti.rigetti_device_capabilities", - "version": "1", - }, - "service": { - "executionWindows": [ - { - "executionDay": "Everyday", - "windowStartHour": "11:00", - "windowEndHour": "12:00", + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + "deviceLocation": "us-east-1", + }, + "action": { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": ["H"], } - ], - "shotsRange": [1, 10], - "deviceLocation": "us-east-1", - }, - "action": { - "braket.ir.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - "supportedOperations": ["H"], - } - }, - "paradigm": { - "qubitCount": 30, - "nativeGateSet": ["ccnot", "cy"], - "connectivity": {"fullyConnected": False, - "connectivityGraph": {"1": ["2", "3"]}}, - }, - "deviceParameters": { - "properties": {"braketSchemaHeader": {"const": - {"name": "braket.device_schema.rigetti.rigetti_device_parameters", - "version": "1"} - }}, - "definitions": {"GateModelParameters": {"properties": - {"braketSchemaHeader": {"const": - {"name": "braket.device_schema.gate_model_parameters", - "version": "1"} - }}}}, + }, + "paradigm": { + "qubitCount": 30, + "nativeGateSet": ["ccnot", "cy"], + "connectivity": { + "fullyConnected": False, + "connectivityGraph": {"1": ["2", "3"]}, + }, + }, + "deviceParameters": { + "properties": { + "braketSchemaHeader": { + "const": { + "name": "braket.device_schema.rigetti.rigetti_device_parameters", + "version": "1", + } + } + }, + "definitions": { + "GateModelParameters": { + "properties": { + "braketSchemaHeader": { + "const": { + "name": "braket.device_schema.gate_model_parameters", + "version": "1", + } + } + } + } + }, }, } ) @@ -105,9 +125,10 @@ creds = { 'AWS_ACCESS_KEY_ID': 'aws_access_key_id', 'AWS_SECRET_KEY': 'aws_secret_key', - } +} +@has_boto3 @patch('boto3.client') @pytest.mark.parametrize("var_device", ['SV1', 'Aspen-8', 'IonQ Device']) def test_awsbraket_get_engine_list(mock_boto3_client, var_device): @@ -116,11 +137,11 @@ def test_awsbraket_get_engine_list(mock_boto3_client, var_device): mock_boto3_client.search_devices.return_value = search_value mock_boto3_client.get_device.return_value = device_value - engine_list = projectq.setups.awsbraket.get_engine_list(credentials=creds, - device=var_device) + engine_list = projectq.setups.awsbraket.get_engine_list(credentials=creds, device=var_device) assert len(engine_list) == 12 +@has_boto3 @patch('boto3.client') def test_awsbraket_error(mock_boto3_client): @@ -129,5 +150,4 @@ def test_awsbraket_error(mock_boto3_client): mock_boto3_client.get_device.return_value = device_value with pytest.raises(projectq.setups.awsbraket.DeviceOfflineError): - projectq.setups.awsbraket.get_engine_list(credentials=creds, - device='Imaginary') + projectq.setups.awsbraket.get_engine_list(credentials=creds, device='Imaginary') diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index de557a065..5fabb8dbd 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,58 +13,62 @@ # See the License for the specific language governing permissions and # limitations under the License. -from . import (arb1qubit2rzandry, - barrier, - carb1qubit2cnotrzandry, - crz2cxandrz, - cnot2rxx, - cnot2cz, - cnu2toffoliandcu, - entangle, - globalphase, - h2rx, - ph2r, - qubitop2onequbit, - qft2crandhadamard, - r2rzandph, - rx2rz, - ry2rz, - rz2rx, - sqrtswap2cnot, - stateprep2cnot, - swap2cnot, - toffoli2cnotandtgate, - time_evolution, - uniformlycontrolledr2cnot, - phaseestimation, - amplitudeamplification) +from . import ( + arb1qubit2rzandry, + barrier, + carb1qubit2cnotrzandry, + crz2cxandrz, + cnot2rxx, + cnot2cz, + cnu2toffoliandcu, + entangle, + globalphase, + h2rx, + ph2r, + qubitop2onequbit, + qft2crandhadamard, + r2rzandph, + rx2rz, + ry2rz, + rz2rx, + sqrtswap2cnot, + stateprep2cnot, + swap2cnot, + toffoli2cnotandtgate, + time_evolution, + uniformlycontrolledr2cnot, + phaseestimation, + amplitudeamplification, +) all_defined_decomposition_rules = [ rule - for module in [arb1qubit2rzandry, - barrier, - carb1qubit2cnotrzandry, - crz2cxandrz, - cnot2rxx, - cnot2cz, - cnu2toffoliandcu, - entangle, - globalphase, - h2rx, - ph2r, - qubitop2onequbit, - qft2crandhadamard, - r2rzandph, - rx2rz, - ry2rz, - rz2rx, - sqrtswap2cnot, - stateprep2cnot, - swap2cnot, - toffoli2cnotandtgate, - time_evolution, - uniformlycontrolledr2cnot, - phaseestimation, - amplitudeamplification] + for module in [ + arb1qubit2rzandry, + barrier, + carb1qubit2cnotrzandry, + crz2cxandrz, + cnot2rxx, + cnot2cz, + cnu2toffoliandcu, + entangle, + globalphase, + h2rx, + ph2r, + qubitop2onequbit, + qft2crandhadamard, + r2rzandph, + rx2rz, + ry2rz, + rz2rx, + sqrtswap2cnot, + stateprep2cnot, + swap2cnot, + toffoli2cnotandtgate, + time_evolution, + uniformlycontrolledr2cnot, + phaseestimation, + amplitudeamplification, + ] for rule in module.all_defined_decomposition_rules ] diff --git a/projectq/setups/decompositions/_gates_test.py b/projectq/setups/decompositions/_gates_test.py index 6eb0b93b3..3d03d2bc7 100755 --- a/projectq/setups/decompositions/_gates_test.py +++ b/projectq/setups/decompositions/_gates_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,25 +12,44 @@ # 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 decompositions rules (using the Simulator). """ import pytest -from projectq.cengines import (MainEngine, - InstructionFilter, - AutoReplacer, - DummyEngine, - DecompositionRuleSet) +from projectq.cengines import ( + MainEngine, + InstructionFilter, + AutoReplacer, + DummyEngine, + DecompositionRuleSet, +) from projectq.backends import Simulator -from projectq.ops import (All, ClassicalInstructionGate, CRz, Entangle, H, - Measure, Ph, R, Rz, T, Tdag, Toffoli, X) +from projectq.ops import ( + All, + ClassicalInstructionGate, + CRz, + Entangle, + H, + Measure, + Ph, + R, + Rz, + T, + Tdag, + Toffoli, + X, +) from projectq.meta import Control -from projectq.setups.decompositions import (crz2cxandrz, entangle, - globalphase, ph2r, r2rzandph, - toffoli2cnotandtgate) +from projectq.setups.decompositions import ( + crz2cxandrz, + entangle, + globalphase, + ph2r, + r2rzandph, + toffoli2cnotandtgate, +) def low_level_gates(eng, cmd): @@ -37,8 +57,7 @@ def low_level_gates(eng, cmd): if isinstance(g, ClassicalInstructionGate): return True if len(cmd.control_qubits) == 0: - if (g == T or g == Tdag or g == H or isinstance(g, Rz) or - isinstance(g, Ph)): + if g == T or g == Tdag or g == H or isinstance(g, Rz) or isinstance(g, Ph): return True else: if len(cmd.control_qubits) == 1 and cmd.gate == X: @@ -49,28 +68,27 @@ def low_level_gates(eng, cmd): def test_entangle(): rule_set = DecompositionRuleSet(modules=[entangle]) sim = Simulator() - eng = MainEngine(sim, - [AutoReplacer(rule_set), - InstructionFilter(low_level_gates)]) + eng = MainEngine(sim, [AutoReplacer(rule_set), InstructionFilter(low_level_gates)]) qureg = eng.allocate_qureg(4) Entangle | qureg - assert .5 == pytest.approx(abs(sim.cheat()[1][0])**2) - assert .5 == pytest.approx(abs(sim.cheat()[1][-1])**2) + assert 0.5 == pytest.approx(abs(sim.cheat()[1][0]) ** 2) + assert 0.5 == pytest.approx(abs(sim.cheat()[1][-1]) ** 2) All(Measure) | qureg def low_level_gates_noglobalphase(eng, cmd): - return (low_level_gates(eng, cmd) and not isinstance(cmd.gate, Ph) and not - isinstance(cmd.gate, R)) + return low_level_gates(eng, cmd) and not isinstance(cmd.gate, Ph) and not isinstance(cmd.gate, R) def test_globalphase(): rule_set = DecompositionRuleSet(modules=[globalphase, r2rzandph]) dummy = DummyEngine(save_commands=True) - eng = MainEngine(dummy, [AutoReplacer(rule_set), - InstructionFilter(low_level_gates_noglobalphase)]) + eng = MainEngine( + dummy, + [AutoReplacer(rule_set), InstructionFilter(low_level_gates_noglobalphase)], + ) qubit = eng.allocate_qubit() R(1.2) | qubit @@ -99,14 +117,12 @@ def run_circuit(eng): def test_gate_decompositions(): sim = Simulator() eng = MainEngine(sim, []) - rule_set = DecompositionRuleSet( - modules=[r2rzandph, crz2cxandrz, toffoli2cnotandtgate, ph2r]) + rule_set = DecompositionRuleSet(modules=[r2rzandph, crz2cxandrz, toffoli2cnotandtgate, ph2r]) qureg = run_circuit(eng) sim2 = Simulator() - eng_lowlevel = MainEngine(sim2, [AutoReplacer(rule_set), - InstructionFilter(low_level_gates)]) + eng_lowlevel = MainEngine(sim2, [AutoReplacer(rule_set), InstructionFilter(low_level_gates)]) qureg2 = run_circuit(eng_lowlevel) for i in range(len(sim.cheat()[1])): diff --git a/projectq/setups/decompositions/amplitudeamplification.py b/projectq/setups/decompositions/amplitudeamplification.py index 517aadeed..6472639db 100644 --- a/projectq/setups/decompositions/amplitudeamplification.py +++ b/projectq/setups/decompositions/amplitudeamplification.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -68,17 +69,16 @@ def func_oracle(eng,system_qubits,qaa_ancilla): """ import math -import numpy as np from projectq.cengines import DecompositionRule -from projectq.meta import Control, Compute, Uncompute, CustomUncompute, Dagger +from projectq.meta import Control, Compute, CustomUncompute, Dagger from projectq.ops import X, Z, Ph, All from projectq.ops import QAA def _decompose_QAA(cmd): - """ Decompose the Quantum Amplitude Apmplification algorithm as a gate. """ + """Decompose the Quantum Amplitude Apmplification algorithm as a gate.""" eng = cmd.engine # System-qubit is the first qubit/qureg. Ancilla qubit is the second qubit diff --git a/projectq/setups/decompositions/amplitudeamplification_test.py b/projectq/setups/decompositions/amplitudeamplification_test.py index f99681713..9599c3982 100644 --- a/projectq/setups/decompositions/amplitudeamplification_test.py +++ b/projectq/setups/decompositions/amplitudeamplification_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2019 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +18,10 @@ import math import pytest -from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, MainEngine) +from projectq.cengines import AutoReplacer, DecompositionRuleSet, MainEngine -from projectq.ops import (X, H, Ry, All, Measure) +from projectq.ops import X, H, Ry, All, Measure from projectq.meta import Loop, Control, Compute, Uncompute from projectq.ops import QAA @@ -44,10 +44,12 @@ def simple_oracle(eng, system_q, control): def test_simple_grover(): rule_set = DecompositionRuleSet(modules=[aa]) - eng = MainEngine(backend=Simulator(), - engine_list=[ - AutoReplacer(rule_set), - ]) + eng = MainEngine( + backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + ], + ) system_qubits = eng.allocate_qureg(7) @@ -71,8 +73,8 @@ def test_simple_grover(): # Theta is calculated previously using get_probability # We calculate also the theoretical final probability # of getting the good state - num_it = int(math.pi / (4. * theta_before) + 1) - theoretical_prob = math.sin((2 * num_it + 1.) * theta_before)**2 + num_it = int(math.pi / (4.0 * theta_before) + 1) + theoretical_prob = math.sin((2 * num_it + 1.0) * theta_before) ** 2 with Loop(eng, num_it): QAA(hache_algorithm, simple_oracle) | (system_qubits, control) @@ -85,14 +87,15 @@ def test_simple_grover(): All(Measure) | system_qubits H | control Measure | control - result = [int(q) for q in system_qubits] - control_result = int(control) eng.flush() - assert total_prob_after == pytest.approx(theoretical_prob, abs=1e-6), ( - "The obtained probability is less than expected %f vs. %f" % - (total_prob_after, theoretical_prob)) + assert total_prob_after == pytest.approx( + theoretical_prob, abs=1e-6 + ), "The obtained probability is less than expected %f vs. %f" % ( + total_prob_after, + theoretical_prob, + ) def complex_algorithm(eng, qreg): @@ -121,10 +124,12 @@ def complex_oracle(eng, system_q, control): def test_complex_aa(): rule_set = DecompositionRuleSet(modules=[aa]) - eng = MainEngine(backend=Simulator(), - engine_list=[ - AutoReplacer(rule_set), - ]) + eng = MainEngine( + backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + ], + ) system_qubits = eng.allocate_qureg(6) @@ -149,8 +154,8 @@ def test_complex_aa(): # Theta is calculated previously using get_probability # We calculate also the theoretical final probability # of getting the good state - num_it = int(math.pi / (4. * theta_before) + 1) - theoretical_prob = math.sin((2 * num_it + 1.) * theta_before)**2 + num_it = int(math.pi / (4.0 * theta_before) + 1) + theoretical_prob = math.sin((2 * num_it + 1.0) * theta_before) ** 2 with Loop(eng, num_it): QAA(complex_algorithm, complex_oracle) | (system_qubits, control) @@ -164,19 +169,19 @@ def test_complex_aa(): All(Measure) | system_qubits H | control Measure | control - result = [int(q) for q in system_qubits] - control_result = int(control) eng.flush() - assert total_prob_after == pytest.approx(theoretical_prob, abs=1e-2), ( - "The obtained probability is less than expected %f vs. %f" % - (total_prob_after, theoretical_prob)) + assert total_prob_after == pytest.approx( + theoretical_prob, abs=1e-2 + ), "The obtained probability is less than expected %f vs. %f" % ( + total_prob_after, + theoretical_prob, + ) def test_string_functions(): algorithm = hache_algorithm oracle = simple_oracle gate = QAA(algorithm, oracle) - assert (str(gate) == - "QAA(Algorithm = hache_algorithm, Oracle = simple_oracle)") + assert str(gate) == "QAA(Algorithm = hache_algorithm, Oracle = simple_oracle)" diff --git a/projectq/setups/decompositions/arb1qubit2rzandry.py b/projectq/setups/decompositions/arb1qubit2rzandry.py index 6d3ea6e43..ba20c98a7 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry.py @@ -1,4 +1,5 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# -*- coding: utf-8 -*- +# Copyright 2017, 2021 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. @@ -11,7 +12,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. - """ Registers the Z-Y decomposition for an arbitrary one qubit gate. @@ -30,14 +30,12 @@ import itertools import math - import numpy from projectq.cengines import DecompositionRule from projectq.meta import Control, get_control_count from projectq.ops import BasicGate, Ph, Ry, Rz - TOLERANCE = 1e-12 @@ -55,7 +53,7 @@ def _recognize_arb1qubit(cmd): return True else: return False - except: + except AttributeError: return False @@ -77,11 +75,17 @@ def _test_parameters(matrix, a, b_half, c_half, d_half): Returns: True if matrix elements of U and `matrix` are TOLERANCE close. """ - U = [[cmath.exp(1j*(a-b_half-d_half))*math.cos(c_half), - -cmath.exp(1j*(a-b_half+d_half))*math.sin(c_half)], - [cmath.exp(1j*(a+b_half-d_half))*math.sin(c_half), - cmath.exp(1j*(a+b_half+d_half))*math.cos(c_half)]] - return numpy.allclose(U, matrix, rtol=10*TOLERANCE, atol=TOLERANCE) + U = [ + [ + cmath.exp(1j * (a - b_half - d_half)) * math.cos(c_half), + -cmath.exp(1j * (a - b_half + d_half)) * math.sin(c_half), + ], + [ + cmath.exp(1j * (a + b_half - d_half)) * math.sin(c_half), + cmath.exp(1j * (a + b_half + d_half)) * math.cos(c_half), + ], + ] + return numpy.allclose(U, matrix, rtol=10 * TOLERANCE, atol=TOLERANCE) def _find_parameters(matrix): @@ -105,70 +109,79 @@ def _find_parameters(matrix): # Note: everything is modulo 2pi. # Case 1: sin(c/2) == 0: if abs(matrix[0][1]) < TOLERANCE: - two_a = cmath.phase(matrix[0][0]*matrix[1][1]) % (2*math.pi) - if abs(two_a) < TOLERANCE or abs(two_a) > 2*math.pi-TOLERANCE: + two_a = cmath.phase(matrix[0][0] * matrix[1][1]) % (2 * math.pi) + if abs(two_a) < TOLERANCE or abs(two_a) > 2 * math.pi - TOLERANCE: # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, # w.l.g. we can choose a==0 because (see U above) # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. a = 0 else: - a = two_a/2. + a = two_a / 2.0 d_half = 0 # w.l.g - b = cmath.phase(matrix[1][1])-cmath.phase(matrix[0][0]) - possible_b_half = [(b/2.) % (2*math.pi), (b/2.+math.pi) % (2*math.pi)] + b = cmath.phase(matrix[1][1]) - cmath.phase(matrix[0][0]) + possible_b_half = [ + (b / 2.0) % (2 * math.pi), + (b / 2.0 + math.pi) % (2 * math.pi), + ] # As we have fixed a, we need to find correct sign for cos(c/2) possible_c_half = [0.0, math.pi] found = False - for b_half, c_half in itertools.product(possible_b_half, - possible_c_half): + for b_half, c_half in itertools.product(possible_b_half, possible_c_half): if _test_parameters(matrix, a, b_half, c_half, d_half): found = True break if not found: - raise Exception("Couldn't find parameters for matrix ", matrix, - "This shouldn't happen. Maybe the matrix is " + - "not unitary?") + raise Exception( + "Couldn't find parameters for matrix ", + matrix, + "This shouldn't happen. Maybe the matrix is " + "not unitary?", + ) # Case 2: cos(c/2) == 0: elif abs(matrix[0][0]) < TOLERANCE: - two_a = cmath.phase(-matrix[0][1]*matrix[1][0]) % (2*math.pi) - if abs(two_a) < TOLERANCE or abs(two_a) > 2*math.pi-TOLERANCE: + two_a = cmath.phase(-matrix[0][1] * matrix[1][0]) % (2 * math.pi) + if abs(two_a) < TOLERANCE or abs(two_a) > 2 * math.pi - TOLERANCE: # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, # w.l.g. we can choose a==0 because (see U above) # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. a = 0 else: - a = two_a/2. + a = two_a / 2.0 d_half = 0 # w.l.g - b = cmath.phase(matrix[1][0])-cmath.phase(matrix[0][1]) + math.pi - possible_b_half = [(b/2.) % (2*math.pi), (b/2.+math.pi) % (2*math.pi)] + b = cmath.phase(matrix[1][0]) - cmath.phase(matrix[0][1]) + math.pi + possible_b_half = [ + (b / 2.0) % (2 * math.pi), + (b / 2.0 + math.pi) % (2 * math.pi), + ] # As we have fixed a, we need to find correct sign for sin(c/2) - possible_c_half = [math.pi/2., 3./2.*math.pi] + possible_c_half = [math.pi / 2.0, 3.0 / 2.0 * math.pi] found = False - for b_half, c_half in itertools.product(possible_b_half, - possible_c_half): + for b_half, c_half in itertools.product(possible_b_half, possible_c_half): if _test_parameters(matrix, a, b_half, c_half, d_half): found = True break if not found: - raise Exception("Couldn't find parameters for matrix ", matrix, - "This shouldn't happen. Maybe the matrix is " + - "not unitary?") + raise Exception( + "Couldn't find parameters for matrix ", + matrix, + "This shouldn't happen. Maybe the matrix is " + "not unitary?", + ) # Case 3: sin(c/2) != 0 and cos(c/2) !=0: else: - two_a = cmath.phase(matrix[0][0]*matrix[1][1]) % (2*math.pi) - if abs(two_a) < TOLERANCE or abs(two_a) > 2*math.pi-TOLERANCE: + two_a = cmath.phase(matrix[0][0] * matrix[1][1]) % (2 * math.pi) + if abs(two_a) < TOLERANCE or abs(two_a) > 2 * math.pi - TOLERANCE: # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, # w.l.g. we can choose a==0 because (see U above) # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. a = 0 else: - a = two_a/2. - two_d = 2.*cmath.phase(matrix[0][1])-2.*cmath.phase(matrix[0][0]) + a = two_a / 2.0 + two_d = 2.0 * cmath.phase(matrix[0][1]) - 2.0 * cmath.phase(matrix[0][0]) + # yapf: disable possible_d_half = [two_d/4. % (2*math.pi), (two_d/4.+math.pi/2.) % (2*math.pi), (two_d/4.+math.pi) % (2*math.pi), (two_d/4.+3./2.*math.pi) % (2*math.pi)] - two_b = 2.*cmath.phase(matrix[1][0])-2.*cmath.phase(matrix[0][0]) + two_b = 2. * cmath.phase(matrix[1][0]) - 2. * cmath.phase(matrix[0][0]) possible_b_half = [two_b/4. % (2*math.pi), (two_b/4.+math.pi/2.) % (2*math.pi), (two_b/4.+math.pi) % (2*math.pi), @@ -178,17 +191,18 @@ def _find_parameters(matrix): (tmp+math.pi) % (2*math.pi), (-1.*tmp) % (2*math.pi), (-1.*tmp+math.pi) % (2*math.pi)] + # yapf: enable found = False - for b_half, c_half, d_half in itertools.product(possible_b_half, - possible_c_half, - possible_d_half): + for b_half, c_half, d_half in itertools.product(possible_b_half, possible_c_half, possible_d_half): if _test_parameters(matrix, a, b_half, c_half, d_half): found = True break if not found: - raise Exception("Couldn't find parameters for matrix ", matrix, - "This shouldn't happen. Maybe the matrix is " + - "not unitary?") + raise Exception( + "Couldn't find parameters for matrix ", + matrix, + "This shouldn't happen. Maybe the matrix is " + "not unitary?", + ) return (a, b_half, c_half, d_half) @@ -209,17 +223,15 @@ def _decompose_arb1qubit(cmd): qb = cmd.qubits eng = cmd.engine with Control(eng, cmd.control_qubits): - if Rz(2*d_half) != Rz(0): - Rz(2*d_half) | qb - if Ry(2*c_half) != Ry(0): - Ry(2*c_half) | qb - if Rz(2*b_half) != Rz(0): - Rz(2*b_half) | qb + if Rz(2 * d_half) != Rz(0): + Rz(2 * d_half) | qb + if Ry(2 * c_half) != Ry(0): + Ry(2 * c_half) | qb + if Rz(2 * b_half) != Rz(0): + Rz(2 * b_half) | qb if a != 0: Ph(a) | qb #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(BasicGate, _decompose_arb1qubit, _recognize_arb1qubit) -] +all_defined_decomposition_rules = [DecompositionRule(BasicGate, _decompose_arb1qubit, _recognize_arb1qubit)] diff --git a/projectq/setups/decompositions/arb1qubit2rzandry_test.py b/projectq/setups/decompositions/arb1qubit2rzandry_test.py index 02ec907d6..6d2daa52a 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry_test.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,10 +22,25 @@ import pytest from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - DummyEngine, InstructionFilter, MainEngine) -from projectq.ops import (BasicGate, ClassicalInstructionGate, MatrixGate, - Measure, Ph, R, Rx, Ry, Rz, X) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, + MainEngine, +) +from projectq.ops import ( + BasicGate, + ClassicalInstructionGate, + MatrixGate, + Measure, + Ph, + R, + Rx, + Ry, + Rz, + X, +) from projectq.meta import Control from . import arb1qubit2rzandry as arb1q @@ -52,8 +68,7 @@ def test_recognize_incorrect_gates(): BasicGate() | qubit # Two qubit gate: two_qubit_gate = MatrixGate() - two_qubit_gate.matrix = [[1, 0, 0, 0], [0, 1, 0, 0], - [0, 0, 1, 0], [0, 0, 0, 1]] + two_qubit_gate.matrix = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]] two_qubit_gate | qubit # Controlled single qubit gate: ctrl_qubit = eng.allocate_qubit() @@ -69,9 +84,7 @@ def z_y_decomp_gates(eng, cmd): if isinstance(g, ClassicalInstructionGate): return True if len(cmd.control_qubits) == 0: - if (isinstance(cmd.gate, Ry) or - isinstance(cmd.gate, Rz) or - isinstance(cmd.gate, Ph)): + if isinstance(cmd.gate, Ry) or isinstance(cmd.gate, Rz) or isinstance(cmd.gate, Ph): return True return False @@ -93,24 +106,28 @@ def create_unitary_matrix(a, b, c, d): Returns: 2x2 matrix as nested lists """ - ph = exp(1j*a) # global phase - return [[ph * exp(1j*b) * math.cos(d), ph * exp(1j*c) * math.sin(d)], - [ph * -exp(-1j*c) * math.sin(d), ph * exp(-1j*b) * math.cos(d)]] + ph = exp(1j * a) # global phase + return [ + [ph * exp(1j * b) * math.cos(d), ph * exp(1j * c) * math.sin(d)], + [ph * -exp(-1j * c) * math.sin(d), ph * exp(-1j * b) * math.cos(d)], + ] def create_test_matrices(): - params = [(0.2, 0.3, 0.5, math.pi * 0.4), - (1e-14, 0.3, 0.5, 0), - (0.4, 0.0, math.pi * 2, 0.7), - (0.0, 0.2, math.pi * 1.2, 1.5), # element of SU(2) - (0.4, 0.0, math.pi * 1.3, 0.8), - (0.4, 4.1, math.pi * 1.3, 0), - (5.1, 1.2, math.pi * 1.5, math.pi/2.), - (1e-13, 1.2, math.pi * 3.7, math.pi/2.), - (0, math.pi/2., 0, 0), - (math.pi/2., -math.pi/2., 0, 0), - (math.pi/2., math.pi/2., 0.1, 0.4), - (math.pi*1.5, math.pi/2., 0, 0.4)] + params = [ + (0.2, 0.3, 0.5, math.pi * 0.4), + (1e-14, 0.3, 0.5, 0), + (0.4, 0.0, math.pi * 2, 0.7), + (0.0, 0.2, math.pi * 1.2, 1.5), # element of SU(2) + (0.4, 0.0, math.pi * 1.3, 0.8), + (0.4, 4.1, math.pi * 1.3, 0), + (5.1, 1.2, math.pi * 1.5, math.pi / 2.0), + (1e-13, 1.2, math.pi * 3.7, math.pi / 2.0), + (0, math.pi / 2.0, 0, 0), + (math.pi / 2.0, -math.pi / 2.0, 0, 0), + (math.pi / 2.0, math.pi / 2.0, 0.1, 0.4), + (math.pi * 1.5, math.pi / 2.0, 0, 0.4), + ] matrices = [] for a, b, c, d in params: matrices.append(create_unitary_matrix(a, b, c, d)) @@ -125,15 +142,18 @@ def test_decomposition(gate_matrix): test_gate.matrix = np.matrix(gate_matrix) correct_dummy_eng = DummyEngine(save_commands=True) - correct_eng = MainEngine(backend=Simulator(), - engine_list=[correct_dummy_eng]) + correct_eng = MainEngine(backend=Simulator(), engine_list=[correct_dummy_eng]) rule_set = DecompositionRuleSet(modules=[arb1q]) test_dummy_eng = DummyEngine(save_commands=True) - test_eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(z_y_decomp_gates), - test_dummy_eng]) + test_eng = MainEngine( + backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(z_y_decomp_gates), + test_dummy_eng, + ], + ) correct_qb = correct_eng.allocate_qubit() correct_eng.flush() @@ -161,16 +181,15 @@ def test_decomposition(gate_matrix): Measure | correct_qb -@pytest.mark.parametrize("gate_matrix", [[[2, 0], [0, 4]], - [[0, 2], [4, 0]], - [[1, 2], [4, 0]]]) +@pytest.mark.parametrize("gate_matrix", [[[2, 0], [0, 4]], [[0, 2], [4, 0]], [[1, 2], [4, 0]]]) def test_decomposition_errors(gate_matrix): test_gate = MatrixGate() test_gate.matrix = np.matrix(gate_matrix) rule_set = DecompositionRuleSet(modules=[arb1q]) - eng = MainEngine(backend=DummyEngine(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(z_y_decomp_gates)]) + eng = MainEngine( + backend=DummyEngine(), + engine_list=[AutoReplacer(rule_set), InstructionFilter(z_y_decomp_gates)], + ) qb = eng.allocate_qubit() with pytest.raises(Exception): test_gate | qb diff --git a/projectq/setups/decompositions/barrier.py b/projectq/setups/decompositions/barrier.py index 359e5e1aa..5f4c05439 100755 --- a/projectq/setups/decompositions/barrier.py +++ b/projectq/setups/decompositions/barrier.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition rule for barriers. @@ -23,16 +23,14 @@ def _decompose_barrier(cmd): - """ Throw out all barriers if they are not supported. """ + """Throw out all barriers if they are not supported.""" pass def _recognize_barrier(cmd): - """ Recognize all barriers. """ + """Recognize all barriers.""" return True #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(BarrierGate, _decompose_barrier, _recognize_barrier) -] +all_defined_decomposition_rules = [DecompositionRule(BarrierGate, _decompose_barrier, _recognize_barrier)] diff --git a/projectq/setups/decompositions/barrier_test.py b/projectq/setups/decompositions/barrier_test.py index 646d6fabe..c7b3ca158 100755 --- a/projectq/setups/decompositions/barrier_test.py +++ b/projectq/setups/decompositions/barrier_test.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# -*- codingf53: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +13,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 barrier.py """ @@ -50,7 +51,6 @@ def my_is_available(cmd): Barrier | qubit eng.flush(deallocate_qubits=True) # Don't test initial allocate and trailing deallocate and flush gate. - count = 0 for cmd in saving_backend.received_commands[1:-2]: assert not cmd.gate == Barrier assert len(saving_backend.received_commands[1:-2]) == 1 diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py index deaddd9db..e0fb14780 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py @@ -1,4 +1,5 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) +# -*- coding: utf-8 -*- +# Copyright 2017, 2021 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. @@ -11,7 +12,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. - """ Registers the decomposition of an controlled arbitary single qubit gate. @@ -28,21 +28,20 @@ from projectq.cengines import DecompositionRule from projectq.meta import get_control_count, Control -from projectq.ops import BasicGate, CNOT, Ph, Ry, Rz, X +from projectq.ops import BasicGate, Ph, Ry, Rz, X from projectq.setups.decompositions import arb1qubit2rzandry as arb1q - TOLERANCE = 1e-12 def _recognize_carb1qubit(cmd): - """ Recognize single controlled one qubit gates with a matrix.""" + """Recognize single controlled one qubit gates with a matrix.""" if get_control_count(cmd) == 1: try: m = cmd.gate.matrix if len(m) == 2: return True - except: + except AttributeError: return False return False @@ -64,11 +63,17 @@ def _test_parameters(matrix, a, b, c_half): Returns: True if matrix elements of V and `matrix` are TOLERANCE close. """ - V = [[-math.sin(c_half)*cmath.exp(1j*a), - cmath.exp(1j*(a-b))*math.cos(c_half)], - [cmath.exp(1j*(a+b))*math.cos(c_half), - cmath.exp(1j*a) * math.sin(c_half)]] - return numpy.allclose(V, matrix, rtol=10*TOLERANCE, atol=TOLERANCE) + V = [ + [ + -math.sin(c_half) * cmath.exp(1j * a), + cmath.exp(1j * (a - b)) * math.cos(c_half), + ], + [ + cmath.exp(1j * (a + b)) * math.cos(c_half), + cmath.exp(1j * a) * math.sin(c_half), + ], + ] + return numpy.allclose(V, matrix, rtol=10 * TOLERANCE, atol=TOLERANCE) def _recognize_v(matrix): @@ -84,17 +89,19 @@ def _recognize_v(matrix): False if it is not possible otherwise (a, b, c/2) """ if abs(matrix[0][0]) < TOLERANCE: - two_a = cmath.phase(matrix[0][1]*matrix[1][0]) % (2*math.pi) - if abs(two_a) < TOLERANCE or abs(two_a) > 2*math.pi-TOLERANCE: + two_a = cmath.phase(matrix[0][1] * matrix[1][0]) % (2 * math.pi) + if abs(two_a) < TOLERANCE or abs(two_a) > 2 * math.pi - TOLERANCE: # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, # w.l.g. we can choose a==0 because (see U above) # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. a = 0 else: - a = two_a/2. - two_b = cmath.phase(matrix[1][0])-cmath.phase(matrix[0][1]) - possible_b = [(two_b/2.) % (2*math.pi), - (two_b/2.+math.pi) % (2*math.pi)] + a = two_a / 2.0 + two_b = cmath.phase(matrix[1][0]) - cmath.phase(matrix[0][1]) + possible_b = [ + (two_b / 2.0) % (2 * math.pi), + (two_b / 2.0 + math.pi) % (2 * math.pi), + ] possible_c_half = [0, math.pi] found = False for b, c_half in itertools.product(possible_b, possible_c_half): @@ -105,16 +112,16 @@ def _recognize_v(matrix): return (a, b, c_half) elif abs(matrix[0][1]) < TOLERANCE: - two_a = cmath.phase(-matrix[0][0] * matrix[1][1]) % (2*math.pi) - if abs(two_a) < TOLERANCE or abs(two_a) > 2*math.pi-TOLERANCE: + two_a = cmath.phase(-matrix[0][0] * matrix[1][1]) % (2 * math.pi) + if abs(two_a) < TOLERANCE or abs(two_a) > 2 * math.pi - TOLERANCE: # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, # w.l.g. we can choose a==0 because (see U above) # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. a = 0 else: - a = two_a/2. + a = two_a / 2.0 b = 0 - possible_c_half = [math.pi/2., 3./2.*math.pi] + possible_c_half = [math.pi / 2.0, 3.0 / 2.0 * math.pi] found = False for c_half in possible_c_half: if _test_parameters(matrix, a, b, c_half): @@ -123,22 +130,26 @@ def _recognize_v(matrix): return False else: - two_a = cmath.phase(-1.*matrix[0][0]*matrix[1][1]) % (2*math.pi) - if abs(two_a) < TOLERANCE or abs(two_a) > 2*math.pi-TOLERANCE: + two_a = cmath.phase(-1.0 * matrix[0][0] * matrix[1][1]) % (2 * math.pi) + if abs(two_a) < TOLERANCE or abs(two_a) > 2 * math.pi - TOLERANCE: # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, # w.l.g. we can choose a==0 because (see U above) # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. a = 0 else: - a = two_a/2. - two_b = cmath.phase(matrix[1][0])-cmath.phase(matrix[0][1]) - possible_b = [(two_b/2.) % (2*math.pi), - (two_b/2.+math.pi) % (2*math.pi)] + a = two_a / 2.0 + two_b = cmath.phase(matrix[1][0]) - cmath.phase(matrix[0][1]) + possible_b = [ + (two_b / 2.0) % (2 * math.pi), + (two_b / 2.0 + math.pi) % (2 * math.pi), + ] tmp = math.acos(abs(matrix[1][0])) + # yapf: disable possible_c_half = [tmp % (2*math.pi), (tmp+math.pi) % (2*math.pi), (-1.*tmp) % (2*math.pi), (-1.*tmp+math.pi) % (2*math.pi)] + # yapf: enable found = False for b, c_half in itertools.product(possible_b, possible_c_half): if _test_parameters(matrix, a, b, c_half): @@ -202,14 +213,14 @@ def _decompose_carb1qubit(cmd): # Case 2: General matrix U: else: a, b_half, c_half, d_half = arb1q._find_parameters(matrix) - d = 2*d_half - b = 2*b_half - if Rz((d-b)/2.) != Rz(0): - Rz((d-b)/2.) | qb + d = 2 * d_half + b = 2 * b_half + if Rz((d - b) / 2.0) != Rz(0): + Rz((d - b) / 2.0) | qb with Control(eng, cmd.control_qubits): X | qb - if Rz(-(d+b)/2.) != Rz(0): - Rz(-(d+b)/2.) | qb + if Rz(-(d + b) / 2.0) != Rz(0): + Rz(-(d + b) / 2.0) | qb if Ry(-c_half) != Ry(0): Ry(-c_half) | qb with Control(eng, cmd.control_qubits): @@ -224,6 +235,4 @@ def _decompose_carb1qubit(cmd): #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(BasicGate, _decompose_carb1qubit, _recognize_carb1qubit) -] +all_defined_decomposition_rules = [DecompositionRule(BasicGate, _decompose_carb1qubit, _recognize_carb1qubit)] diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py index 44b29f526..280df747b 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +19,28 @@ import pytest from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - DummyEngine, InstructionFilter, MainEngine) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, + MainEngine, +) from projectq.meta import Control -from projectq.ops import (All, BasicGate, ClassicalInstructionGate, - MatrixGate, Measure, Ph, R, Rx, Ry, Rz, X, XGate) +from projectq.ops import ( + All, + BasicGate, + ClassicalInstructionGate, + MatrixGate, + Measure, + Ph, + R, + Rx, + Ry, + Rz, + X, + XGate, +) from projectq.setups.decompositions import arb1qubit2rzandry_test as arb1q_t from . import carb1qubit2cnotrzandry as carb1q @@ -58,8 +76,7 @@ def test_recognize_incorrect_gates(): BasicGate() | qubit # Two qubit gate: two_qubit_gate = MatrixGate() - two_qubit_gate.matrix = np.matrix([[1, 0, 0, 0], [0, 1, 0, 0], - [0, 0, 1, 0], [0, 0, 0, 1]]) + two_qubit_gate.matrix = np.matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) two_qubit_gate | qubit with Control(eng, ctrl_qureg): # Too many Control qubits: @@ -74,21 +91,20 @@ def _decomp_gates(eng, cmd): if isinstance(g, ClassicalInstructionGate): return True if len(cmd.control_qubits) == 0: - if (isinstance(cmd.gate, Ry) or - isinstance(cmd.gate, Rz) or - isinstance(cmd.gate, Ph)): + if isinstance(cmd.gate, Ry) or isinstance(cmd.gate, Rz) or isinstance(cmd.gate, Ph): return True if len(cmd.control_qubits) == 1: - if (isinstance(cmd.gate, XGate) or - isinstance(cmd.gate, Ph)): + if isinstance(cmd.gate, XGate) or isinstance(cmd.gate, Ph): return True return False +# yapf: disable @pytest.mark.parametrize("gate_matrix", [[[1, 0], [0, -1]], [[0, -1j], [1j, 0]]]) def test_recognize_v(gate_matrix): assert carb1q._recognize_v(gate_matrix) +# yapf: enable @pytest.mark.parametrize("gate_matrix", arb1q_t.create_test_matrices()) @@ -97,18 +113,20 @@ def test_decomposition(gate_matrix): test_gate = MatrixGate() test_gate.matrix = np.matrix(gate_matrix) - for basis_state in ([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], - [0, 0, 0, 1]): + for basis_state in ([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]): correct_dummy_eng = DummyEngine(save_commands=True) - correct_eng = MainEngine(backend=Simulator(), - engine_list=[correct_dummy_eng]) + correct_eng = MainEngine(backend=Simulator(), engine_list=[correct_dummy_eng]) rule_set = DecompositionRuleSet(modules=[carb1q]) test_dummy_eng = DummyEngine(save_commands=True) - test_eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(_decomp_gates), - test_dummy_eng]) + 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 @@ -119,8 +137,7 @@ def test_decomposition(gate_matrix): test_ctrl_qb = test_eng.allocate_qubit() test_eng.flush() - correct_sim.set_wavefunction(basis_state, correct_qb + - correct_ctrl_qb) + correct_sim.set_wavefunction(basis_state, correct_qb + correct_ctrl_qb) test_sim.set_wavefunction(basis_state, test_qb + test_ctrl_qb) with Control(test_eng, test_ctrl_qb): @@ -136,8 +153,7 @@ def test_decomposition(gate_matrix): for fstate in ['00', '01', '10', '11']: test = test_sim.get_amplitude(fstate, test_qb + test_ctrl_qb) - correct = correct_sim.get_amplitude(fstate, correct_qb + - correct_ctrl_qb) + correct = correct_sim.get_amplitude(fstate, correct_qb + correct_ctrl_qb) assert correct == pytest.approx(test, rel=1e-12, abs=1e-12) All(Measure) | test_qb + test_ctrl_qb diff --git a/projectq/setups/decompositions/cnot2cz.py b/projectq/setups/decompositions/cnot2cz.py index e2ad2ef30..3525e83b8 100644 --- a/projectq/setups/decompositions/cnot2cz.py +++ b/projectq/setups/decompositions/cnot2cz.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition to for a CNOT gate in terms of CZ and Hadamard. """ @@ -22,7 +22,7 @@ def _decompose_cnot(cmd): - """ Decompose CNOT gates. """ + """Decompose CNOT gates.""" ctrl = cmd.control_qubits eng = cmd.engine with Compute(eng): @@ -36,6 +36,4 @@ def _recognize_cnot(cmd): #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(X.__class__, _decompose_cnot, _recognize_cnot) -] +all_defined_decomposition_rules = [DecompositionRule(X.__class__, _decompose_cnot, _recognize_cnot)] diff --git a/projectq/setups/decompositions/cnot2cz_test.py b/projectq/setups/decompositions/cnot2cz_test.py index f838cf4c5..9f50959ab 100644 --- a/projectq/setups/decompositions/cnot2cz_test.py +++ b/projectq/setups/decompositions/cnot2cz_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,11 +17,14 @@ import pytest -import projectq from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, - InstructionFilter) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, +) from projectq.meta import Control from projectq.ops import All, CNOT, CZ, Measure, X, Z @@ -50,7 +54,6 @@ def test_recognize_gates(): def _decomp_gates(eng, cmd): - g = cmd.gate if len(cmd.control_qubits) == 1 and isinstance(cmd.gate, X.__class__): return False return True @@ -59,16 +62,19 @@ def _decomp_gates(eng, cmd): def test_cnot_decomposition(): for basis_state_index in range(0, 4): basis_state = [0] * 4 - basis_state[basis_state_index] = 1. + basis_state[basis_state_index] = 1.0 correct_dummy_eng = DummyEngine(save_commands=True) - correct_eng = MainEngine(backend=Simulator(), - engine_list=[correct_dummy_eng]) + correct_eng = MainEngine(backend=Simulator(), engine_list=[correct_dummy_eng]) rule_set = DecompositionRuleSet(modules=[cnot2cz]) test_dummy_eng = DummyEngine(save_commands=True) - test_eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(_decomp_gates), - test_dummy_eng]) + 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() @@ -78,8 +84,7 @@ def test_cnot_decomposition(): test_ctrl_qb = test_eng.allocate_qubit() test_eng.flush() - correct_sim.set_wavefunction(basis_state, correct_qb + - correct_ctrl_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) @@ -92,10 +97,8 @@ def test_cnot_decomposition(): 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) + test = test_sim.get_amplitude(binary_state, test_qb + test_ctrl_qb) + correct = correct_sim.get_amplitude(binary_state, correct_qb + correct_ctrl_qb) assert correct == pytest.approx(test, rel=1e-12, abs=1e-12) All(Measure) | test_qb + test_ctrl_qb diff --git a/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py index a1fa2e6ac..92b3a0598 100644 --- a/projectq/setups/decompositions/cnot2rxx.py +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,7 +27,7 @@ def _decompose_cnot2rxx_M(cmd): - """ Decompose CNOT gate into Rxx gate. """ + """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] @@ -38,7 +39,7 @@ def _decompose_cnot2rxx_M(cmd): def _decompose_cnot2rxx_P(cmd): - """ Decompose CNOT gate into Rxx gate. """ + """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] @@ -50,12 +51,12 @@ def _decompose_cnot2rxx_P(cmd): def _recognize_cnot2(cmd): - """ Identify that the command is a CNOT gate (control - X gate)""" + """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) + DecompositionRule(X.__class__, _decompose_cnot2rxx_P, _recognize_cnot2), ] diff --git a/projectq/setups/decompositions/cnot2rxx_test.py b/projectq/setups/decompositions/cnot2rxx_test.py index bc0d0c077..31fcbdd42 100644 --- a/projectq/setups/decompositions/cnot2rxx_test.py +++ b/projectq/setups/decompositions/cnot2rxx_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,12 +16,15 @@ "Tests for projectq.setups.decompositions.cnot2rxx.py." import pytest -import numpy as np from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, - InstructionFilter) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, +) from projectq.meta import Control from projectq.ops import All, CNOT, CZ, Measure, X, Z @@ -28,7 +32,7 @@ def test_recognize_correct_gates(): - """Test that recognize_cnot recognizes cnot gates. """ + """Test that recognize_cnot recognizes cnot gates.""" saving_backend = DummyEngine(save_commands=True) eng = MainEngine(backend=saving_backend) qubit1 = eng.allocate_qubit() @@ -51,7 +55,7 @@ def test_recognize_correct_gates(): def _decomp_gates(eng, cmd): - """ Test that the cmd.gate is a gate of class X """ + """Test that the cmd.gate is a gate of class X""" if len(cmd.control_qubits) == 1 and isinstance(cmd.gate, X.__class__): return False return True @@ -73,27 +77,28 @@ def _decomp_gates(eng, cmd): def test_decomposition(): - """ Test that this decomposition of CNOT produces correct amplitudes + """Test that this decomposition of CNOT produces correct amplitudes - Function tests each DecompositionRule in - cnot2rxx.all_defined_decomposition_rules + 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. + basis_state[basis_state_index] = 1.0 correct_dummy_eng = DummyEngine(save_commands=True) - correct_eng = MainEngine(backend=Simulator(), - engine_list=[correct_dummy_eng]) + 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_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() @@ -103,8 +108,7 @@ def test_decomposition(): test_ctrl_qb = test_eng.allocate_qubit() test_eng.flush() - correct_sim.set_wavefunction(basis_state, - correct_qb + correct_ctrl_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) @@ -115,8 +119,7 @@ def test_decomposition(): assert len(correct_dummy_eng.received_commands) == 5 assert len(test_dummy_eng.received_commands) == 10 - assert correct_eng.backend.cheat()[1] == pytest.approx( - test_eng.backend.cheat()[1], rel=1e-12, abs=1e-12) + assert correct_eng.backend.cheat()[1] == pytest.approx(test_eng.backend.cheat()[1], rel=1e-12, abs=1e-12) All(Measure) | test_qb + test_ctrl_qb All(Measure) | correct_qb + correct_ctrl_qb diff --git a/projectq/setups/decompositions/cnu2toffoliandcu.py b/projectq/setups/decompositions/cnu2toffoliandcu.py index 9ce2610c3..faedeba56 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition rule for multi-controlled gates. @@ -55,13 +55,16 @@ def _decompose_CnU(cmd): # specialized for X-gate if gate == XGate() and n > 2: n -= 1 - ancilla_qureg = eng.allocate_qureg(n-1) + ancilla_qureg = eng.allocate_qureg(n - 1) with Compute(eng): Toffoli | (ctrl_qureg[0], ctrl_qureg[1], ancilla_qureg[0]) for ctrl_index in range(2, n): - Toffoli | (ctrl_qureg[ctrl_index], ancilla_qureg[ctrl_index-2], - ancilla_qureg[ctrl_index-1]) + Toffoli | ( + ctrl_qureg[ctrl_index], + ancilla_qureg[ctrl_index - 2], + ancilla_qureg[ctrl_index - 1], + ) ctrls = [ancilla_qureg[-1]] # specialized for X-gate @@ -74,6 +77,4 @@ def _decompose_CnU(cmd): #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(BasicGate, _decompose_CnU, _recognize_CnU) -] +all_defined_decomposition_rules = [DecompositionRule(BasicGate, _decompose_CnU, _recognize_CnU)] diff --git a/projectq/setups/decompositions/cnu2toffoliandcu_test.py b/projectq/setups/decompositions/cnu2toffoliandcu_test.py index b0e27760f..e6798af7d 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu_test.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +18,25 @@ import pytest from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - DummyEngine, InstructionFilter, MainEngine) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, + MainEngine, +) from projectq.meta import Control -from projectq.ops import (All, ClassicalInstructionGate, Measure, Ph, QFT, Rx, - Ry, X, XGate) +from projectq.ops import ( + All, + ClassicalInstructionGate, + Measure, + Ph, + QFT, + Rx, + Ry, + X, + XGate, +) from . import cnu2toffoliandcu @@ -78,16 +93,19 @@ def _decomp_gates(eng, cmd): def test_decomposition(): for basis_state_index in range(0, 16): basis_state = [0] * 16 - basis_state[basis_state_index] = 1. + basis_state[basis_state_index] = 1.0 correct_dummy_eng = DummyEngine(save_commands=True) - correct_eng = MainEngine(backend=Simulator(), - engine_list=[correct_dummy_eng]) + correct_eng = MainEngine(backend=Simulator(), engine_list=[correct_dummy_eng]) rule_set = DecompositionRuleSet(modules=[cnu2toffoliandcu]) test_dummy_eng = DummyEngine(save_commands=True) - test_eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(_decomp_gates), - test_dummy_eng]) + 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() @@ -97,8 +115,7 @@ def test_decomposition(): test_ctrl_qureg = test_eng.allocate_qureg(3) test_eng.flush() - correct_sim.set_wavefunction(basis_state, correct_qb + - correct_ctrl_qureg) + correct_sim.set_wavefunction(basis_state, correct_qb + correct_ctrl_qureg) test_sim.set_wavefunction(basis_state, test_qb + test_ctrl_qureg) with Control(test_eng, test_ctrl_qureg[:2]): @@ -123,10 +140,8 @@ def test_decomposition(): for fstate in range(16): binary_state = format(fstate, '04b') - test = test_sim.get_amplitude(binary_state, - test_qb + test_ctrl_qureg) - correct = correct_sim.get_amplitude(binary_state, correct_qb + - correct_ctrl_qureg) + test = test_sim.get_amplitude(binary_state, test_qb + test_ctrl_qureg) + correct = correct_sim.get_amplitude(binary_state, correct_qb + correct_ctrl_qureg) assert correct == pytest.approx(test, rel=1e-12, abs=1e-12) All(Measure) | test_qb + test_ctrl_qureg diff --git a/projectq/setups/decompositions/crz2cxandrz.py b/projectq/setups/decompositions/crz2cxandrz.py index c12f43f63..98e2f342e 100755 --- a/projectq/setups/decompositions/crz2cxandrz.py +++ b/projectq/setups/decompositions/crz2cxandrz.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition for controlled z-rotation gates. @@ -24,7 +24,7 @@ def _decompose_CRz(cmd): - """ Decompose the controlled Rz gate (into CNOT and Rz). """ + """Decompose the controlled Rz gate (into CNOT and Rz).""" qubit = cmd.qubits[0] ctrl = cmd.control_qubits gate = cmd.gate @@ -37,11 +37,9 @@ def _decompose_CRz(cmd): def _recognize_CRz(cmd): - """ Recognize the controlled Rz gate. """ + """Recognize the controlled Rz gate.""" return get_control_count(cmd) >= 1 #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(Rz, _decompose_CRz, _recognize_CRz) -] +all_defined_decomposition_rules = [DecompositionRule(Rz, _decompose_CRz, _recognize_CRz)] diff --git a/projectq/setups/decompositions/entangle.py b/projectq/setups/decompositions/entangle.py index b7049ea99..59943ab1d 100755 --- a/projectq/setups/decompositions/entangle.py +++ b/projectq/setups/decompositions/entangle.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition for the Entangle gate. @@ -20,12 +20,12 @@ """ from projectq.cengines import DecompositionRule -from projectq.meta import Control, get_control_count +from projectq.meta import Control from projectq.ops import X, H, Entangle, All def _decompose_entangle(cmd): - """ Decompose the entangle gate. """ + """Decompose the entangle gate.""" qr = cmd.qubits[0] eng = cmd.engine @@ -36,6 +36,4 @@ def _decompose_entangle(cmd): #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(Entangle.__class__, _decompose_entangle) -] +all_defined_decomposition_rules = [DecompositionRule(Entangle.__class__, _decompose_entangle)] diff --git a/projectq/setups/decompositions/globalphase.py b/projectq/setups/decompositions/globalphase.py index b75bd13a4..3b546a361 100755 --- a/projectq/setups/decompositions/globalphase.py +++ b/projectq/setups/decompositions/globalphase.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition rule for global phases. @@ -24,16 +24,14 @@ def _decompose_PhNoCtrl(cmd): - """ Throw out global phases (no controls). """ + """Throw out global phases (no controls).""" pass def _recognize_PhNoCtrl(cmd): - """ Recognize global phases (no controls). """ + """Recognize global phases (no controls).""" return get_control_count(cmd) == 0 #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(Ph, _decompose_PhNoCtrl, _recognize_PhNoCtrl) -] +all_defined_decomposition_rules = [DecompositionRule(Ph, _decompose_PhNoCtrl, _recognize_PhNoCtrl)] diff --git a/projectq/setups/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py index b54533bad..68b7f6866 100644 --- a/projectq/setups/decompositions/h2rx.py +++ b/projectq/setups/decompositions/h2rx.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,31 +28,31 @@ def _decompose_h2rx_M(cmd): - """ Decompose the Ry gate.""" + """Decompose the Ry gate.""" # Labelled 'M' for 'minus' because decomposition ends with a Ry(-pi/2) qubit = cmd.qubits[0] Rx(math.pi) | qubit - Ph(math.pi/2) | qubit + Ph(math.pi / 2) | qubit Ry(-1 * math.pi / 2) | qubit def _decompose_h2rx_N(cmd): - """ Decompose the Ry gate.""" + """Decompose the Ry gate.""" # Labelled 'N' for 'neutral' because decomposition doesn't end with # Ry(pi/2) or Ry(-pi/2) qubit = cmd.qubits[0] Ry(math.pi / 2) | qubit - Ph(3*math.pi/2) | qubit + Ph(3 * math.pi / 2) | qubit Rx(-1 * math.pi) | qubit def _recognize_HNoCtrl(cmd): - """ For efficiency reasons only if no control qubits.""" + """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) + DecompositionRule(H.__class__, _decompose_h2rx_M, _recognize_HNoCtrl), ] diff --git a/projectq/setups/decompositions/h2rx_test.py b/projectq/setups/decompositions/h2rx_test.py index 2df048801..ff06b277e 100644 --- a/projectq/setups/decompositions/h2rx_test.py +++ b/projectq/setups/decompositions/h2rx_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,22 +15,24 @@ "Tests for projectq.setups.decompositions.h2rx.py" -import numpy as np - import pytest from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, - InstructionFilter) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, +) from projectq.meta import Control -from projectq.ops import Measure, X, H, HGate +from projectq.ops import Measure, H, HGate from . import h2rx def test_recognize_correct_gates(): - """ Test that recognize_HNoCtrl recognizes ctrl qubits """ + """Test that recognize_HNoCtrl recognizes ctrl qubits""" saving_backend = DummyEngine(save_commands=True) eng = MainEngine(backend=saving_backend) qubit = eng.allocate_qubit() @@ -44,7 +47,7 @@ def test_recognize_correct_gates(): def h_decomp_gates(eng, cmd): - """ Test that cmd.gate is a gate of class HGate """ + """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 @@ -67,29 +70,30 @@ def h_decomp_gates(eng, cmd): def test_decomposition(): - """ Test that this decomposition of H produces correct amplitudes + """Test that this decomposition of H produces correct amplitudes - Function tests each DecompositionRule in - h2rx.all_defined_decomposition_rules + Function tests each DecompositionRule in + h2rx.all_defined_decomposition_rules """ decomposition_rule_list = h2rx.all_defined_decomposition_rules for rule in decomposition_rule_list: for basis_state_index in range(2): basis_state = [0] * 2 - basis_state[basis_state_index] = 1. + basis_state[basis_state_index] = 1.0 correct_dummy_eng = DummyEngine(save_commands=True) - correct_eng = MainEngine(backend=Simulator(), - engine_list=[correct_dummy_eng]) + 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 - ]) + test_eng = MainEngine( + backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(h_decomp_gates), + test_dummy_eng, + ], + ) correct_qb = correct_eng.allocate_qubit() correct_eng.flush() @@ -105,13 +109,10 @@ def test_decomposition(): correct_eng.flush() test_eng.flush() - assert H in (cmd.gate - for cmd in correct_dummy_eng.received_commands) - assert H not in (cmd.gate - for cmd in test_dummy_eng.received_commands) + assert H in (cmd.gate for cmd in correct_dummy_eng.received_commands) + assert H not in (cmd.gate for cmd in test_dummy_eng.received_commands) - assert correct_eng.backend.cheat()[1] == pytest.approx( - test_eng.backend.cheat()[1], rel=1e-12, abs=1e-12) + assert correct_eng.backend.cheat()[1] == pytest.approx(test_eng.backend.cheat()[1], rel=1e-12, abs=1e-12) Measure | test_qb Measure | correct_qb diff --git a/projectq/setups/decompositions/ph2r.py b/projectq/setups/decompositions/ph2r.py index 5db5d6f6a..4f14e2c26 100755 --- a/projectq/setups/decompositions/ph2r.py +++ b/projectq/setups/decompositions/ph2r.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition for the controlled global phase gate. @@ -25,7 +25,7 @@ def _decompose_Ph(cmd): - """ Decompose the controlled phase gate (C^nPh(phase)). """ + """Decompose the controlled phase gate (C^nPh(phase)).""" ctrl = cmd.control_qubits gate = cmd.gate eng = cmd.engine @@ -35,11 +35,9 @@ def _decompose_Ph(cmd): def _recognize_Ph(cmd): - """ Recognize the controlled phase gate. """ + """Recognize the controlled phase gate.""" return get_control_count(cmd) >= 1 #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(Ph, _decompose_Ph, _recognize_Ph) -] +all_defined_decomposition_rules = [DecompositionRule(Ph, _decompose_Ph, _recognize_Ph)] diff --git a/projectq/setups/decompositions/phaseestimation.py b/projectq/setups/decompositions/phaseestimation.py index faf7523cf..1d7fc3f32 100644 --- a/projectq/setups/decompositions/phaseestimation.py +++ b/projectq/setups/decompositions/phaseestimation.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition for phase estimation. @@ -31,7 +31,7 @@ .. code-block:: python # Example using a ProjectQ gate - + n_qpe_ancillas = 3 qpe_ancillas = eng.allocate_qureg(n_qpe_ancillas) system_qubits = eng.allocate_qureg(1) @@ -80,21 +80,19 @@ def two_qubit_gate(system_q, time): Calling the function with the parameters system_qubits(Qureg) and time (integer), i.e. f(system_qubits, time), applies to the system qubits a unitary defined in f with parameter time. - -""" -import numpy as np +""" from projectq.cengines import DecompositionRule -from projectq.meta import Control, Loop, get_control_count +from projectq.meta import Control, Loop from projectq.ops import H, Tensor, get_inverse, QFT from projectq.ops import QPE def _decompose_QPE(cmd): - """ Decompose the Quantum Phase Estimation gate. """ + """Decompose the Quantum Phase Estimation gate.""" eng = cmd.engine # Ancillas is the first qubit/qureg. System-qubit is the second qubit/qureg @@ -108,14 +106,14 @@ def _decompose_QPE(cmd): U = cmd.gate.unitary # Control U on the system_qubits - if (callable(U)): + if callable(U): # If U is a function for i in range(len(qpe_ancillas)): with Control(eng, qpe_ancillas[i]): - U(system_qubits, time=2**i) + U(system_qubits, time=2 ** i) else: for i in range(len(qpe_ancillas)): - ipower = int(2**i) + ipower = int(2 ** i) with Loop(eng, ipower): with Control(eng, qpe_ancillas[i]): U | system_qubits @@ -123,7 +121,6 @@ def _decompose_QPE(cmd): # Inverse QFT on the ancillas get_inverse(QFT) | qpe_ancillas + #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(QPE, _decompose_QPE) -] +all_defined_decomposition_rules = [DecompositionRule(QPE, _decompose_QPE)] diff --git a/projectq/setups/decompositions/phaseestimation_test.py b/projectq/setups/decompositions/phaseestimation_test.py index 1b8f8e63c..43aa5ec16 100644 --- a/projectq/setups/decompositions/phaseestimation_test.py +++ b/projectq/setups/decompositions/phaseestimation_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,16 +19,15 @@ 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.cengines import ( + AutoReplacer, + DecompositionRuleSet, + MainEngine, +) -from projectq.ops import X, H, All, Measure, Tensor, Ph, CNOT, StatePreparation +from projectq.ops import X, H, All, Measure, Tensor, Ph, CNOT, StatePreparation, QPE -from projectq.ops import (BasicGate) - -from projectq.ops import QPE from projectq.setups.decompositions import phaseestimation as pe from projectq.setups.decompositions import qft2crandhadamard as dqft import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot @@ -36,9 +36,12 @@ def test_simple_test_X_eigenvectors(): rule_set = DecompositionRuleSet(modules=[pe, dqft]) - eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - ]) + eng = MainEngine( + backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + ], + ) results = np.array([]) for i in range(100): autovector = eng.allocate_qureg(1) @@ -51,24 +54,30 @@ def test_simple_test_X_eigenvectors(): fasebinlist = [int(q) for q in ancillas] fasebin = ''.join(str(j) for j in fasebinlist) faseint = int(fasebin, 2) - phase = faseint / (2. ** (len(ancillas))) + phase = faseint / (2.0 ** (len(ancillas))) results = np.append(results, phase) All(Measure) | autovector eng.flush() num_phase = (results == 0.5).sum() - assert num_phase/100. >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % (num_phase/100., 0.35) + assert num_phase / 100.0 >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( + num_phase / 100.0, + 0.35, + ) def test_Ph_eigenvectors(): rule_set = DecompositionRuleSet(modules=[pe, dqft]) - eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - ]) + eng = MainEngine( + backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + ], + ) results = np.array([]) for i in range(100): autovector = eng.allocate_qureg(1) - theta = cmath.pi*2.*0.125 + theta = cmath.pi * 2.0 * 0.125 unit = Ph(theta) ancillas = eng.allocate_qureg(3) QPE(unit) | (ancillas, autovector) @@ -76,26 +85,32 @@ def test_Ph_eigenvectors(): fasebinlist = [int(q) for q in ancillas] fasebin = ''.join(str(j) for j in fasebinlist) faseint = int(fasebin, 2) - phase = faseint / (2. ** (len(ancillas))) + phase = faseint / (2.0 ** (len(ancillas))) results = np.append(results, phase) All(Measure) | autovector eng.flush() num_phase = (results == 0.125).sum() - assert num_phase/100. >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % (num_phase/100., 0.35) + assert num_phase / 100.0 >= 0.35, "Statistics phase calculation are not correct (%f vs. %f)" % ( + num_phase / 100.0, + 0.35, + ) def two_qubit_gate(system_q, time): CNOT | (system_q[0], system_q[1]) - Ph(2.0*cmath.pi*(time * 0.125)) | system_q[1] + Ph(2.0 * cmath.pi * (time * 0.125)) | system_q[1] CNOT | (system_q[0], system_q[1]) def test_2qubitsPh_andfunction_eigenvectors(): rule_set = DecompositionRuleSet(modules=[pe, dqft]) - eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - ]) + eng = MainEngine( + backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + ], + ) results = np.array([]) for i in range(100): autovector = eng.allocate_qureg(2) @@ -106,27 +121,33 @@ def test_2qubitsPh_andfunction_eigenvectors(): fasebinlist = [int(q) for q in ancillas] fasebin = ''.join(str(j) for j in fasebinlist) faseint = int(fasebin, 2) - phase = faseint / (2. ** (len(ancillas))) + phase = faseint / (2.0 ** (len(ancillas))) results = np.append(results, phase) All(Measure) | autovector eng.flush() num_phase = (results == 0.125).sum() - assert num_phase/100. >= 0.34, "Statistics phase calculation are not correct (%f vs. %f)" % (num_phase/100., 0.34) + assert num_phase / 100.0 >= 0.34, "Statistics phase calculation are not correct (%f vs. %f)" % ( + num_phase / 100.0, + 0.34, + ) def test_X_no_eigenvectors(): rule_set = DecompositionRuleSet(modules=[pe, dqft, stateprep2cnot, ucr2cnot]) - eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - ]) + eng = MainEngine( + backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + ], + ) results = np.array([]) results_plus = np.array([]) results_minus = np.array([]) for i in range(100): autovector = eng.allocate_qureg(1) - amplitude0 = (np.sqrt(2) + np.sqrt(6))/4. - amplitude1 = (np.sqrt(2) - np.sqrt(6))/4. + amplitude0 = (np.sqrt(2) + np.sqrt(6)) / 4.0 + amplitude1 = (np.sqrt(2) - np.sqrt(6)) / 4.0 StatePreparation([amplitude0, amplitude1]) | autovector unit = X ancillas = eng.allocate_qureg(1) @@ -135,15 +156,15 @@ def test_X_no_eigenvectors(): fasebinlist = [int(q) for q in ancillas] fasebin = ''.join(str(j) for j in fasebinlist) faseint = int(fasebin, 2) - phase = faseint / (2. ** (len(ancillas))) + phase = faseint / (2.0 ** (len(ancillas))) results = np.append(results, phase) Tensor(H) | autovector - if np.allclose(phase, .0, rtol=1e-1): + if np.allclose(phase, 0.0, rtol=1e-1): results_plus = np.append(results_plus, phase) All(Measure) | autovector autovector_result = int(autovector) assert autovector_result == 0 - elif np.allclose(phase, .5, rtol=1e-1): + elif np.allclose(phase, 0.5, rtol=1e-1): results_minus = np.append(results_minus, phase) All(Measure) | autovector autovector_result = int(autovector) @@ -151,12 +172,17 @@ def test_X_no_eigenvectors(): eng.flush() total = len(results_plus) + len(results_minus) - plus_probability = len(results_plus)/100. + plus_probability = len(results_plus) / 100.0 assert total == pytest.approx(100, abs=5) - assert plus_probability == pytest.approx(1./4., abs = 1e-1), "Statistics on |+> probability are not correct (%f vs. %f)" % (plus_probability, 1./4.) + assert plus_probability == pytest.approx( + 1.0 / 4.0, abs=1e-1 + ), "Statistics on |+> probability are not correct (%f vs. %f)" % ( + plus_probability, + 1.0 / 4.0, + ) def test_string(): unit = X gate = QPE(unit) - assert (str(gate) == "QPE(X)") + assert str(gate) == "QPE(X)" diff --git a/projectq/setups/decompositions/qft2crandhadamard.py b/projectq/setups/decompositions/qft2crandhadamard.py index 63537dea8..5119e19ee 100755 --- a/projectq/setups/decompositions/qft2crandhadamard.py +++ b/projectq/setups/decompositions/qft2crandhadamard.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition rule for the quantum Fourier transform. @@ -41,6 +41,4 @@ def _decompose_QFT(cmd): #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(QFT.__class__, _decompose_QFT) -] +all_defined_decomposition_rules = [DecompositionRule(QFT.__class__, _decompose_QFT)] diff --git a/projectq/setups/decompositions/qubitop2onequbit.py b/projectq/setups/decompositions/qubitop2onequbit.py index 61f7954e7..4e24effad 100644 --- a/projectq/setups/decompositions/qubitop2onequbit.py +++ b/projectq/setups/decompositions/qubitop2onequbit.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition rule for a unitary QubitOperator to one qubit gates. """ @@ -24,7 +24,7 @@ def _recognize_qubitop(cmd): - """ For efficiency only use this if at most 1 control qubit.""" + """For efficiency only use this if at most 1 control qubit.""" return get_control_count(cmd) <= 1 @@ -34,7 +34,7 @@ def _decompose_qubitop(cmd): eng = cmd.engine qubit_op = cmd.gate with Control(eng, cmd.control_qubits): - (term, coefficient), = qubit_op.terms.items() + ((term, coefficient),) = qubit_op.terms.items() phase = cmath.phase(coefficient) Ph(phase) | qureg[0] for index, action in term: @@ -47,6 +47,4 @@ def _decompose_qubitop(cmd): #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(QubitOperator, _decompose_qubitop, _recognize_qubitop) -] +all_defined_decomposition_rules = [DecompositionRule(QubitOperator, _decompose_qubitop, _recognize_qubitop)] diff --git a/projectq/setups/decompositions/qubitop2onequbit_test.py b/projectq/setups/decompositions/qubitop2onequbit_test.py index 3b1741cfb..ddf542ffa 100644 --- a/projectq/setups/decompositions/qubitop2onequbit_test.py +++ b/projectq/setups/decompositions/qubitop2onequbit_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +19,15 @@ from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, - InstructionFilter) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, +) from projectq.meta import Control from projectq.ops import All, Measure, Ph, QubitOperator, X, Y, Z - import projectq.setups.decompositions.qubitop2onequbit as qubitop2onequbit @@ -52,25 +56,23 @@ def _decomp_gates(eng, cmd): def test_qubitop2singlequbit(): num_qubits = 4 - random_initial_state = [0.2+0.1*x*cmath.exp(0.1j+0.2j*x) - for x in range(2**(num_qubits+1))] + random_initial_state = [0.2 + 0.1 * x * cmath.exp(0.1j + 0.2j * x) for x in range(2 ** (num_qubits + 1))] rule_set = DecompositionRuleSet(modules=[qubitop2onequbit]) - test_eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(_decomp_gates)]) + test_eng = MainEngine( + backend=Simulator(), + engine_list=[AutoReplacer(rule_set), InstructionFilter(_decomp_gates)], + ) test_qureg = test_eng.allocate_qureg(num_qubits) test_ctrl_qb = test_eng.allocate_qubit() test_eng.flush() - test_eng.backend.set_wavefunction(random_initial_state, - test_qureg + test_ctrl_qb) + test_eng.backend.set_wavefunction(random_initial_state, test_qureg + test_ctrl_qb) correct_eng = MainEngine() correct_qureg = correct_eng.allocate_qureg(num_qubits) correct_ctrl_qb = correct_eng.allocate_qubit() correct_eng.flush() - correct_eng.backend.set_wavefunction(random_initial_state, - correct_qureg + correct_ctrl_qb) + correct_eng.backend.set_wavefunction(random_initial_state, correct_qureg + correct_ctrl_qb) - qubit_op_0 = QubitOperator("X0 Y1 Z3", -1.j) + qubit_op_0 = QubitOperator("X0 Y1 Z3", -1.0j) qubit_op_1 = QubitOperator("Z0 Y1 X3", cmath.exp(0.6j)) qubit_op_0 | test_qureg @@ -86,12 +88,10 @@ def test_qubitop2singlequbit(): X | correct_qureg[3] correct_eng.flush() - for fstate in range(2**(num_qubits+1)): - binary_state = format(fstate, '0' + str(num_qubits+1) + 'b') - test = test_eng.backend.get_amplitude(binary_state, - test_qureg + test_ctrl_qb) - correct = correct_eng.backend.get_amplitude( - binary_state, correct_qureg + correct_ctrl_qb) + for fstate in range(2 ** (num_qubits + 1)): + binary_state = format(fstate, '0' + str(num_qubits + 1) + 'b') + test = test_eng.backend.get_amplitude(binary_state, test_qureg + test_ctrl_qb) + correct = correct_eng.backend.get_amplitude(binary_state, correct_qureg + correct_ctrl_qb) assert correct == pytest.approx(test, rel=1e-10, abs=1e-10) All(Measure) | correct_qureg + correct_ctrl_qb diff --git a/projectq/setups/decompositions/r2rzandph.py b/projectq/setups/decompositions/r2rzandph.py index ab7b4c95e..2e7a94e22 100755 --- a/projectq/setups/decompositions/r2rzandph.py +++ b/projectq/setups/decompositions/r2rzandph.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition rule for the phase-shift gate. @@ -25,17 +25,15 @@ def _decompose_R(cmd): - """ Decompose the (controlled) phase-shift gate, denoted by R(phase). """ + """Decompose the (controlled) phase-shift gate, denoted by R(phase).""" ctrl = cmd.control_qubits eng = cmd.engine gate = cmd.gate with Control(eng, ctrl): - Ph(.5 * gate.angle) | cmd.qubits + Ph(0.5 * gate.angle) | cmd.qubits Rz(gate.angle) | cmd.qubits #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(R, _decompose_R) -] +all_defined_decomposition_rules = [DecompositionRule(R, _decompose_R)] diff --git a/projectq/setups/decompositions/rx2rz.py b/projectq/setups/decompositions/rx2rz.py index 657afa28b..41f5a3c7f 100644 --- a/projectq/setups/decompositions/rx2rz.py +++ b/projectq/setups/decompositions/rx2rz.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition for the Rx gate into an Rz gate and Hadamard. """ @@ -22,7 +22,7 @@ def _decompose_rx(cmd): - """ Decompose the Rx gate.""" + """Decompose the Rx gate.""" qubit = cmd.qubits[0] eng = cmd.engine angle = cmd.gate.angle @@ -35,11 +35,9 @@ def _decompose_rx(cmd): def _recognize_RxNoCtrl(cmd): - """ For efficiency reasons only if no control qubits.""" + """For efficiency reasons only if no control qubits.""" return get_control_count(cmd) == 0 #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(Rx, _decompose_rx, _recognize_RxNoCtrl) -] +all_defined_decomposition_rules = [DecompositionRule(Rx, _decompose_rx, _recognize_RxNoCtrl)] diff --git a/projectq/setups/decompositions/rx2rz_test.py b/projectq/setups/decompositions/rx2rz_test.py index 2d6e5eacb..35896f29f 100644 --- a/projectq/setups/decompositions/rx2rz_test.py +++ b/projectq/setups/decompositions/rx2rz_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +19,16 @@ import pytest -from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - DummyEngine, InstructionFilter, MainEngine) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, + MainEngine, +) from projectq.meta import Control -from projectq.ops import Measure, Ph, Rx +from projectq.ops import Measure, Rx from . import rx2rz @@ -50,19 +55,22 @@ def rx_decomp_gates(eng, cmd): return True -@pytest.mark.parametrize("angle", [0, math.pi, 2*math.pi, 4*math.pi, 0.5]) +@pytest.mark.parametrize("angle", [0, math.pi, 2 * math.pi, 4 * math.pi, 0.5]) def test_decomposition(angle): 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]) + correct_eng = MainEngine(backend=Simulator(), engine_list=[correct_dummy_eng]) rule_set = DecompositionRuleSet(modules=[rx2rz]) test_dummy_eng = DummyEngine(save_commands=True) - test_eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(rx_decomp_gates), - test_dummy_eng]) + test_eng = MainEngine( + backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(rx_decomp_gates), + test_dummy_eng, + ], + ) correct_qb = correct_eng.allocate_qubit() Rx(angle) | correct_qb diff --git a/projectq/setups/decompositions/ry2rz.py b/projectq/setups/decompositions/ry2rz.py index 7ee9e6e01..aa2a0bd65 100644 --- a/projectq/setups/decompositions/ry2rz.py +++ b/projectq/setups/decompositions/ry2rz.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition for the Ry gate into an Rz and Rx(pi/2) gate. """ @@ -20,28 +20,26 @@ from projectq.cengines import DecompositionRule from projectq.meta import Compute, Control, get_control_count, Uncompute -from projectq.ops import Rx, Ry, Rz, H +from projectq.ops import Rx, Ry, Rz def _decompose_ry(cmd): - """ Decompose the Ry gate.""" + """Decompose the Ry gate.""" qubit = cmd.qubits[0] eng = cmd.engine angle = cmd.gate.angle with Control(eng, cmd.control_qubits): with Compute(eng): - Rx(math.pi/2.) | qubit + Rx(math.pi / 2.0) | qubit Rz(angle) | qubit Uncompute(eng) def _recognize_RyNoCtrl(cmd): - """ For efficiency reasons only if no control qubits.""" + """For efficiency reasons only if no control qubits.""" return get_control_count(cmd) == 0 #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(Ry, _decompose_ry, _recognize_RyNoCtrl) -] +all_defined_decomposition_rules = [DecompositionRule(Ry, _decompose_ry, _recognize_RyNoCtrl)] diff --git a/projectq/setups/decompositions/ry2rz_test.py b/projectq/setups/decompositions/ry2rz_test.py index d24692d63..e5e0c909f 100644 --- a/projectq/setups/decompositions/ry2rz_test.py +++ b/projectq/setups/decompositions/ry2rz_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +19,16 @@ import pytest -from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - DummyEngine, InstructionFilter, MainEngine) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, + MainEngine, +) from projectq.meta import Control -from projectq.ops import Measure, Ph, Ry +from projectq.ops import Measure, Ry from . import ry2rz @@ -50,19 +55,22 @@ def ry_decomp_gates(eng, cmd): return True -@pytest.mark.parametrize("angle", [0, math.pi, 2*math.pi, 4*math.pi, 0.5]) +@pytest.mark.parametrize("angle", [0, math.pi, 2 * math.pi, 4 * math.pi, 0.5]) def test_decomposition(angle): 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]) + correct_eng = MainEngine(backend=Simulator(), engine_list=[correct_dummy_eng]) rule_set = DecompositionRuleSet(modules=[ry2rz]) test_dummy_eng = DummyEngine(save_commands=True) - test_eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(ry_decomp_gates), - test_dummy_eng]) + test_eng = MainEngine( + backend=Simulator(), + engine_list=[ + AutoReplacer(rule_set), + InstructionFilter(ry_decomp_gates), + test_dummy_eng, + ], + ) correct_qb = correct_eng.allocate_qubit() Ry(angle) | correct_qb diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index f49ba72e1..afa96b436 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +29,7 @@ def _decompose_rz2rx_P(cmd): - """ Decompose the Rz using negative angle. """ + """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 @@ -36,13 +37,13 @@ def _decompose_rz2rx_P(cmd): with Control(eng, cmd.control_qubits): with Compute(eng): - Ry(-math.pi / 2.) | qubit + Ry(-math.pi / 2.0) | qubit Rx(-angle) | qubit Uncompute(eng) def _decompose_rz2rx_M(cmd): - """ Decompose the Rz using positive angle. """ + """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 @@ -50,18 +51,18 @@ def _decompose_rz2rx_M(cmd): with Control(eng, cmd.control_qubits): with Compute(eng): - Ry(math.pi / 2.) | qubit + Ry(math.pi / 2.0) | 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).""" + """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) + DecompositionRule(Rz, _decompose_rz2rx_M, _recognize_RzNoCtrl), ] diff --git a/projectq/setups/decompositions/rz2rx_test.py b/projectq/setups/decompositions/rz2rx_test.py index 7c6c9962f..f1ca4a826 100644 --- a/projectq/setups/decompositions/rz2rx_test.py +++ b/projectq/setups/decompositions/rz2rx_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +21,12 @@ from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, - InstructionFilter) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, +) from projectq.meta import Control from projectq.ops import Measure, Rz @@ -29,7 +34,7 @@ def test_recognize_correct_gates(): - """ Test that recognize_RzNoCtrl recognizes ctrl qubits """ + """Test that recognize_RzNoCtrl recognizes ctrl qubits""" saving_backend = DummyEngine(save_commands=True) eng = MainEngine(backend=saving_backend) qubit = eng.allocate_qubit() @@ -44,7 +49,7 @@ def test_recognize_correct_gates(): def rz_decomp_gates(eng, cmd): - """ Test that cmd.gate is the gate Rz """ + """Test that cmd.gate is the gate Rz""" g = cmd.gate if isinstance(g, Rz): return False @@ -79,17 +84,18 @@ def test_decomposition(angle): 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]) + 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 - ]) + 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 @@ -117,9 +123,7 @@ def test_decomposition(angle): # 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) + 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/decompositions/sqrtswap2cnot.py b/projectq/setups/decompositions/sqrtswap2cnot.py index 4eed87ff6..f2441c2b0 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot.py +++ b/projectq/setups/decompositions/sqrtswap2cnot.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition to achieve a SqrtSwap gate. """ @@ -22,9 +22,8 @@ def _decompose_sqrtswap(cmd): - """ Decompose (controlled) swap gates.""" - assert (len(cmd.qubits) == 2 and len(cmd.qubits[0]) == 1 and - len(cmd.qubits[1]) == 1) + """Decompose (controlled) swap gates.""" + assert len(cmd.qubits) == 2 and len(cmd.qubits[0]) == 1 and len(cmd.qubits[1]) == 1 ctrl = cmd.control_qubits qubit0 = cmd.qubits[0][0] qubit1 = cmd.qubits[1][0] @@ -39,6 +38,4 @@ def _decompose_sqrtswap(cmd): #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(SqrtSwap.__class__, _decompose_sqrtswap) -] +all_defined_decomposition_rules = [DecompositionRule(SqrtSwap.__class__, _decompose_sqrtswap)] diff --git a/projectq/setups/decompositions/sqrtswap2cnot_test.py b/projectq/setups/decompositions/sqrtswap2cnot_test.py index b804e7e81..02dd3362d 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot_test.py +++ b/projectq/setups/decompositions/sqrtswap2cnot_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,18 +12,19 @@ # 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.sqrtswap2cnot.""" import pytest -import projectq from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, - InstructionFilter) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, +) -from projectq.meta import Compute, Control, Uncompute from projectq.ops import All, Measure, SqrtSwap import projectq.setups.decompositions.sqrtswap2cnot as sqrtswap2cnot @@ -35,17 +37,19 @@ def _decomp_gates(eng, cmd): def test_sqrtswap(): - for basis_state in ([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], - [0, 0, 0, 1]): + for basis_state in ([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]): correct_dummy_eng = DummyEngine(save_commands=True) - correct_eng = MainEngine(backend=Simulator(), - engine_list=[correct_dummy_eng]) + correct_eng = MainEngine(backend=Simulator(), engine_list=[correct_dummy_eng]) rule_set = DecompositionRuleSet(modules=[sqrtswap2cnot]) test_dummy_eng = DummyEngine(save_commands=True) - test_eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(_decomp_gates), - test_dummy_eng]) + 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_qureg = correct_eng.allocate_qureg(2) @@ -61,8 +65,7 @@ def test_sqrtswap(): SqrtSwap | (correct_qureg[0], correct_qureg[1]) correct_eng.flush() - assert (len(test_dummy_eng.received_commands) != - len(correct_dummy_eng.received_commands)) + assert len(test_dummy_eng.received_commands) != len(correct_dummy_eng.received_commands) for fstate in range(4): binary_state = format(fstate, '02b') test = test_sim.get_amplitude(binary_state, test_qureg) diff --git a/projectq/setups/decompositions/stateprep2cnot.py b/projectq/setups/decompositions/stateprep2cnot.py index aa81c2fb7..6073bcc14 100644 --- a/projectq/setups/decompositions/stateprep2cnot.py +++ b/projectq/setups/decompositions/stateprep2cnot.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers decomposition for StatePreparation. """ @@ -21,8 +21,12 @@ from projectq.cengines import DecompositionRule from projectq.meta import Control, Dagger -from projectq.ops import (StatePreparation, Ry, Rz, UniformlyControlledRy, - UniformlyControlledRz, Ph) +from projectq.ops import ( + StatePreparation, + UniformlyControlledRy, + UniformlyControlledRz, + Ph, +) def _decompose_state_preparation(cmd): @@ -34,11 +38,11 @@ def _decompose_state_preparation(cmd): num_qubits = len(cmd.qubits[0]) qureg = cmd.qubits[0] final_state = cmd.gate.final_state - if len(final_state) != 2**num_qubits: + if len(final_state) != 2 ** num_qubits: raise ValueError("Length of final_state is invalid.") - norm = 0. + norm = 0.0 for amplitude in final_state: - norm += abs(amplitude)**2 + norm += abs(amplitude) ** 2 if norm < 1 - 1e-10 or norm > 1 + 1e-10: raise ValueError("final_state is not normalized.") with Control(eng, cmd.control_qubits): @@ -51,13 +55,15 @@ def _decompose_state_preparation(cmd): for target_qubit in range(len(qureg)): angles = [] phase_of_next_blocks = [] - for block in range(2**(len(qureg)-target_qubit-1)): - phase0 = phase_of_blocks[2*block] - phase1 = phase_of_blocks[2*block+1] + for block in range(2 ** (len(qureg) - target_qubit - 1)): + phase0 = phase_of_blocks[2 * block] + phase1 = phase_of_blocks[2 * block + 1] angles.append(phase0 - phase1) - phase_of_next_blocks.append((phase0 + phase1)/2.) - UniformlyControlledRz(angles) | (qureg[(target_qubit+1):], - qureg[target_qubit]) + phase_of_next_blocks.append((phase0 + phase1) / 2.0) + UniformlyControlledRz(angles) | ( + qureg[(target_qubit + 1) :], # noqa: E203 + qureg[target_qubit], + ) phase_of_blocks = phase_of_next_blocks # Cancel global phase Ph(-phase_of_blocks[0]) | qureg[-1] @@ -68,21 +74,20 @@ def _decompose_state_preparation(cmd): for target_qubit in range(len(qureg)): angles = [] abs_of_next_blocks = [] - for block in range(2**(len(qureg)-target_qubit-1)): - a0 = abs_of_blocks[2*block] - a1 = abs_of_blocks[2*block+1] + for block in range(2 ** (len(qureg) - target_qubit - 1)): + a0 = abs_of_blocks[2 * block] + a1 = abs_of_blocks[2 * block + 1] if a0 == 0 and a1 == 0: angles.append(0) else: - angles.append( - -2. * math.acos(a0 / math.sqrt(a0**2 + a1**2))) - abs_of_next_blocks.append(math.sqrt(a0**2 + a1**2)) - UniformlyControlledRy(angles) | (qureg[(target_qubit+1):], - qureg[target_qubit]) + angles.append(-2.0 * math.acos(a0 / math.sqrt(a0 ** 2 + a1 ** 2))) + abs_of_next_blocks.append(math.sqrt(a0 ** 2 + a1 ** 2)) + UniformlyControlledRy(angles) | ( + qureg[(target_qubit + 1) :], # noqa: E203 + qureg[target_qubit], + ) abs_of_blocks = abs_of_next_blocks #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(StatePreparation, _decompose_state_preparation) -] +all_defined_decomposition_rules = [DecompositionRule(StatePreparation, _decompose_state_preparation)] diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py index 81137b169..381414ac0 100644 --- a/projectq/setups/decompositions/stateprep2cnot_test.py +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.setups.decompositions.stateprep2cnot.""" import cmath @@ -43,19 +43,18 @@ def test_wrong_final_state(): @pytest.mark.parametrize("zeros", [True, False]) @pytest.mark.parametrize("n_qubits", [1, 2, 3, 4]) def test_state_preparation(n_qubits, zeros): - engine_list = restrictedgateset.get_engine_list( - one_qubit_gates=(Ry, Rz, Ph)) + engine_list = restrictedgateset.get_engine_list(one_qubit_gates=(Ry, Rz, Ph)) eng = projectq.MainEngine(engine_list=engine_list) qureg = eng.allocate_qureg(n_qubits) eng.flush() - f_state = [0.2+0.1*x*cmath.exp(0.1j+0.2j*x) for x in range(2**n_qubits)] + f_state = [0.2 + 0.1 * x * cmath.exp(0.1j + 0.2j * x) for x in range(2 ** n_qubits)] if zeros: - for i in range(2**(n_qubits-1)): + for i in range(2 ** (n_qubits - 1)): f_state[i] = 0 norm = 0 for amplitude in f_state: - norm += abs(amplitude)**2 + norm += abs(amplitude) ** 2 f_state = [x / math.sqrt(norm) for x in f_state] StatePreparation(f_state) | qureg diff --git a/projectq/setups/decompositions/swap2cnot.py b/projectq/setups/decompositions/swap2cnot.py index c48cbcc6c..c7c438824 100755 --- a/projectq/setups/decompositions/swap2cnot.py +++ b/projectq/setups/decompositions/swap2cnot.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition to achieve a Swap gate. @@ -20,12 +20,12 @@ """ from projectq.cengines import DecompositionRule -from projectq.meta import Compute, Uncompute, Control, get_control_count +from projectq.meta import Compute, Uncompute, Control from projectq.ops import Swap, CNOT def _decompose_swap(cmd): - """ Decompose (controlled) swap gates. """ + """Decompose (controlled) swap gates.""" ctrl = cmd.control_qubits eng = cmd.engine with Compute(eng): @@ -36,6 +36,4 @@ def _decompose_swap(cmd): #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(Swap.__class__, _decompose_swap) -] +all_defined_decomposition_rules = [DecompositionRule(Swap.__class__, _decompose_swap)] diff --git a/projectq/setups/decompositions/time_evolution.py b/projectq/setups/decompositions/time_evolution.py index ce5a91443..6ca4789ce 100644 --- a/projectq/setups/decompositions/time_evolution.py +++ b/projectq/setups/decompositions/time_evolution.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,8 +12,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. - - """ Registers decomposition for the TimeEvolution gates. @@ -24,7 +23,7 @@ from projectq.cengines import DecompositionRule from projectq.meta import Control, Compute, Uncompute -from projectq.ops import TimeEvolution, QubitOperator, H, Y, CNOT, Rz, Rx, Ry +from projectq.ops import TimeEvolution, QubitOperator, H, CNOT, Rz, Rx, Ry def _recognize_time_evolution_commuting_terms(cmd): @@ -41,9 +40,7 @@ def _recognize_time_evolution_commuting_terms(cmd): for other in hamiltonian.terms: other_op = QubitOperator(other, hamiltonian.terms[other]) commutator = test_op * other_op - other_op * test_op - if not commutator.isclose(id_op, - rel_tol=1e-9, - abs_tol=1e-9): + if not commutator.isclose(id_op, rel_tol=1e-9, abs_tol=1e-9): return False return True @@ -99,11 +96,11 @@ def _decompose_time_evolution_individual_terms(cmd): if len(term) == 1: with Control(eng, cmd.control_qubits): if term[0][1] == 'X': - Rx(time * coefficient * 2.) | qureg[term[0][0]] + Rx(time * coefficient * 2.0) | qureg[term[0][0]] elif term[0][1] == 'Y': - Ry(time * coefficient * 2.) | qureg[term[0][0]] + Ry(time * coefficient * 2.0) | qureg[term[0][0]] else: - Rz(time * coefficient * 2.) | qureg[term[0][0]] + Rz(time * coefficient * 2.0) | qureg[term[0][0]] # hamiltonian has more than one local operator else: with Control(eng, cmd.control_qubits): @@ -114,13 +111,13 @@ def _decompose_time_evolution_individual_terms(cmd): if action == 'X': H | qureg[index] elif action == 'Y': - Rx(math.pi / 2.) | qureg[index] + Rx(math.pi / 2.0) | qureg[index] # Check that qureg had exactly as many qubits as indices: assert check_indices == set((range(len(qureg)))) # Compute parity - for i in range(len(qureg)-1): - CNOT | (qureg[i], qureg[i+1]) - Rz(time * coefficient * 2.) | qureg[-1] + for i in range(len(qureg) - 1): + CNOT | (qureg[i], qureg[i + 1]) + Rz(time * coefficient * 2.0) | qureg[-1] # Uncompute parity and basis change Uncompute(eng) @@ -128,15 +125,14 @@ def _decompose_time_evolution_individual_terms(cmd): rule_commuting_terms = DecompositionRule( gate_class=TimeEvolution, gate_decomposer=_decompose_time_evolution_commuting_terms, - gate_recognizer=_recognize_time_evolution_commuting_terms) - + gate_recognizer=_recognize_time_evolution_commuting_terms, +) rule_individual_terms = DecompositionRule( gate_class=TimeEvolution, gate_decomposer=_decompose_time_evolution_individual_terms, - gate_recognizer=_recognize_time_evolution_individual_terms) - + gate_recognizer=_recognize_time_evolution_individual_terms, +) #: Decomposition rules -all_defined_decomposition_rules = [rule_commuting_terms, - rule_individual_terms] +all_defined_decomposition_rules = [rule_commuting_terms, rule_individual_terms] diff --git a/projectq/setups/decompositions/time_evolution_test.py b/projectq/setups/decompositions/time_evolution_test.py index 4a5e30f54..79397e07f 100644 --- a/projectq/setups/decompositions/time_evolution_test.py +++ b/projectq/setups/decompositions/time_evolution_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,12 +24,23 @@ from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import (DummyEngine, AutoReplacer, InstructionFilter, - InstructionFilter, DecompositionRuleSet) +from projectq.cengines import ( + DummyEngine, + AutoReplacer, + InstructionFilter, + DecompositionRuleSet, +) from projectq.meta import Control -from projectq.ops import (QubitOperator, TimeEvolution, - ClassicalInstructionGate, Ph, Rx, Ry, Rz, All, - Measure) +from projectq.ops import ( + QubitOperator, + TimeEvolution, + ClassicalInstructionGate, + Ph, + Rx, + Ry, + All, + Measure, +) from . import time_evolution as te @@ -43,10 +55,10 @@ def test_recognize_commuting_terms(): op4 = QubitOperator("X1 Y2", 0.5) + QubitOperator("X2", 1e-10) op5 = QubitOperator("X1 Y2", 0.5) + QubitOperator("X2", 1e-8) op6 = QubitOperator("X2", 1.0) - TimeEvolution(1., op1 + op2 + op3 + op4) | wavefunction - TimeEvolution(1., op1 + op5) | wavefunction - TimeEvolution(1., op1 + op6) | wavefunction - TimeEvolution(1., op1) | wavefunction + TimeEvolution(1.0, op1 + op2 + op3 + op4) | wavefunction + TimeEvolution(1.0, op1 + op5) | wavefunction + TimeEvolution(1.0, op1 + op6) | wavefunction + TimeEvolution(1.0, op1) | wavefunction cmd1 = saving_backend.received_commands[5] cmd2 = saving_backend.received_commands[6] @@ -63,16 +75,14 @@ def test_decompose_commuting_terms(): saving_backend = DummyEngine(save_commands=True) def my_filter(self, cmd): - if (len(cmd.qubits[0]) <= 2 or - isinstance(cmd.gate, ClassicalInstructionGate)): + if len(cmd.qubits[0]) <= 2 or isinstance(cmd.gate, ClassicalInstructionGate): return True return False rules = DecompositionRuleSet([te.rule_commuting_terms]) replacer = AutoReplacer(rules) filter_eng = InstructionFilter(my_filter) - eng = MainEngine(backend=saving_backend, - engine_list=[replacer, filter_eng]) + eng = MainEngine(backend=saving_backend, engine_list=[replacer, filter_eng]) qureg = eng.allocate_qureg(5) with Control(eng, qureg[3]): op1 = QubitOperator("X1 Y2", 0.7) @@ -88,23 +98,29 @@ def my_filter(self, cmd): scaled_op1 = QubitOperator("X0 Y1", 0.7) scaled_op2 = QubitOperator("Y0 X1", -0.8) for cmd in [cmd1, cmd2, cmd3]: - if (cmd.gate == Ph(- 1.5 * 0.6) and - cmd.qubits[0][0].id == qureg[1].id and # 1st qubit of [1,2,4] - cmd.control_qubits[0].id == qureg[3].id): + if ( + cmd.gate == Ph(-1.5 * 0.6) + and cmd.qubits[0][0].id == qureg[1].id + and cmd.control_qubits[0].id == qureg[3].id # 1st qubit of [1,2,4] + ): found[0] = True - elif (isinstance(cmd.gate, TimeEvolution) and - cmd.gate.hamiltonian.isclose(scaled_op1) and - cmd.gate.time == pytest.approx(1.5) and - cmd.qubits[0][0].id == qureg[1].id and - cmd.qubits[0][1].id == qureg[2].id and - cmd.control_qubits[0].id == qureg[3].id): + elif ( + isinstance(cmd.gate, TimeEvolution) + and cmd.gate.hamiltonian.isclose(scaled_op1) + and cmd.gate.time == pytest.approx(1.5) + and cmd.qubits[0][0].id == qureg[1].id + and cmd.qubits[0][1].id == qureg[2].id + and cmd.control_qubits[0].id == qureg[3].id + ): found[1] = True - elif (isinstance(cmd.gate, TimeEvolution) and - cmd.gate.hamiltonian.isclose(scaled_op2) and - cmd.gate.time == pytest.approx(1.5) and - cmd.qubits[0][0].id == qureg[2].id and - cmd.qubits[0][1].id == qureg[4].id and - cmd.control_qubits[0].id == qureg[3].id): + elif ( + isinstance(cmd.gate, TimeEvolution) + and cmd.gate.hamiltonian.isclose(scaled_op2) + and cmd.gate.time == pytest.approx(1.5) + and cmd.qubits[0][0].id == qureg[2].id + and cmd.qubits[0][1].id == qureg[4].id + and cmd.control_qubits[0].id == qureg[3].id + ): found[2] = True assert all(found) @@ -116,9 +132,9 @@ def test_recognize_individual_terms(): op1 = QubitOperator("X1 Y2", 0.5) op2 = QubitOperator("Y2 X4", -0.5) op3 = QubitOperator("X2", 1.0) - TimeEvolution(1., op1 + op2) | wavefunction - TimeEvolution(1., op2) | wavefunction - TimeEvolution(1., op3) | wavefunction + TimeEvolution(1.0, op1 + op2) | wavefunction + TimeEvolution(1.0, op2) | wavefunction + TimeEvolution(1.0, op3) | wavefunction cmd1 = saving_backend.received_commands[5] cmd2 = saving_backend.received_commands[6] @@ -133,15 +149,14 @@ def test_decompose_individual_terms(): saving_eng = DummyEngine(save_commands=True) def my_filter(self, cmd): - if (isinstance(cmd.gate, TimeEvolution)): + if isinstance(cmd.gate, TimeEvolution): return False return True rules = DecompositionRuleSet([te.rule_individual_terms]) replacer = AutoReplacer(rules) filter_eng = InstructionFilter(my_filter) - eng = MainEngine(backend=Simulator(), - engine_list=[replacer, filter_eng, saving_eng]) + eng = MainEngine(backend=Simulator(), engine_list=[replacer, filter_eng, saving_eng]) qureg = eng.allocate_qureg(5) # initialize in random wavefunction by applying some gates: Rx(0.1) | qureg[0] @@ -174,6 +189,7 @@ def my_filter(self, cmd): eng.flush() qbit_to_bit_map5, final_wavefunction5 = copy.deepcopy(eng.backend.cheat()) All(Measure) | qureg + # Check manually: def build_matrix(list_single_matrices): @@ -183,12 +199,11 @@ def build_matrix(list_single_matrices): return res.tocsc() id_sp = sps.identity(2, format="csc", dtype=complex) - x_sp = sps.csc_matrix([[0., 1.], [1., 0.]], dtype=complex) - y_sp = sps.csc_matrix([[0., -1.j], [1.j, 0.]], dtype=complex) - z_sp = sps.csc_matrix([[1., 0.], [0., -1.]], dtype=complex) + x_sp = sps.csc_matrix([[0.0, 1.0], [1.0, 0.0]], dtype=complex) + y_sp = sps.csc_matrix([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) + z_sp = sps.csc_matrix([[1.0, 0.0], [0.0, -1.0]], dtype=complex) - matrix1 = (sps.identity(2**5, format="csc", dtype=complex) * 0.6 * - 1.1 * -1.0j) + matrix1 = sps.identity(2 ** 5, format="csc", dtype=complex) * 0.6 * 1.1 * -1.0j step1 = scipy.sparse.linalg.expm(matrix1).dot(init_wavefunction) assert numpy.allclose(step1, final_wavefunction1) diff --git a/projectq/setups/decompositions/toffoli2cnotandtgate.py b/projectq/setups/decompositions/toffoli2cnotandtgate.py index 38c33a8be..94b42bcab 100755 --- a/projectq/setups/decompositions/toffoli2cnotandtgate.py +++ b/projectq/setups/decompositions/toffoli2cnotandtgate.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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. - """ Registers a decomposition rule for the Toffoli gate. @@ -24,9 +24,8 @@ def _decompose_toffoli(cmd): - """ Decompose the Toffoli gate into CNOT, H, T, and Tdagger gates. """ + """Decompose the Toffoli gate into CNOT, H, T, and Tdagger gates.""" ctrl = cmd.control_qubits - eng = cmd.engine target = cmd.qubits[0] c1 = ctrl[0] @@ -50,11 +49,9 @@ def _decompose_toffoli(cmd): def _recognize_toffoli(cmd): - """ Recognize the Toffoli gate. """ + """Recognize the Toffoli gate.""" return get_control_count(cmd) == 2 #: Decomposition rules -all_defined_decomposition_rules = [ - DecompositionRule(NOT.__class__, _decompose_toffoli, _recognize_toffoli) -] +all_defined_decomposition_rules = [DecompositionRule(NOT.__class__, _decompose_toffoli, _recognize_toffoli)] diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py index 0f27d2455..a1ce53d85 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,20 +12,16 @@ # 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 decomposition for UnformlyControlledRy and UnformlyControlledRz. """ from projectq.cengines import DecompositionRule -from projectq.meta import Compute, Control, Uncompute, CustomUncompute -from projectq.ops import (CNOT, Ry, Rz, - UniformlyControlledRy, - UniformlyControlledRz) +from projectq.meta import Compute, Control, CustomUncompute +from projectq.ops import CNOT, Ry, Rz, UniformlyControlledRy, UniformlyControlledRz -def _apply_ucr_n(angles, ucontrol_qubits, target_qubit, eng, gate_class, - rightmost_cnot): +def _apply_ucr_n(angles, ucontrol_qubits, target_qubit, eng, gate_class, rightmost_cnot): if len(ucontrol_qubits) == 0: gate = gate_class(angles[0]) if gate != gate_class(0): @@ -33,25 +30,27 @@ def _apply_ucr_n(angles, ucontrol_qubits, target_qubit, eng, gate_class, if rightmost_cnot[len(ucontrol_qubits)]: angles1 = [] angles2 = [] - for lower_bits in range(2**(len(ucontrol_qubits)-1)): + for lower_bits in range(2 ** (len(ucontrol_qubits) - 1)): leading_0 = angles[lower_bits] - leading_1 = angles[lower_bits + 2**(len(ucontrol_qubits)-1)] - angles1.append((leading_0 + leading_1)/2.) - angles2.append((leading_0 - leading_1)/2.) + leading_1 = angles[lower_bits + 2 ** (len(ucontrol_qubits) - 1)] + angles1.append((leading_0 + leading_1) / 2.0) + angles2.append((leading_0 - leading_1) / 2.0) else: angles1 = [] angles2 = [] - for lower_bits in range(2**(len(ucontrol_qubits)-1)): + for lower_bits in range(2 ** (len(ucontrol_qubits) - 1)): leading_0 = angles[lower_bits] - leading_1 = angles[lower_bits + 2**(len(ucontrol_qubits)-1)] - angles1.append((leading_0 - leading_1)/2.) - angles2.append((leading_0 + leading_1)/2.) - _apply_ucr_n(angles=angles1, - ucontrol_qubits=ucontrol_qubits[:-1], - target_qubit=target_qubit, - eng=eng, - gate_class=gate_class, - rightmost_cnot=rightmost_cnot) + leading_1 = angles[lower_bits + 2 ** (len(ucontrol_qubits) - 1)] + angles1.append((leading_0 - leading_1) / 2.0) + angles2.append((leading_0 + leading_1) / 2.0) + _apply_ucr_n( + angles=angles1, + ucontrol_qubits=ucontrol_qubits[:-1], + target_qubit=target_qubit, + eng=eng, + gate_class=gate_class, + rightmost_cnot=rightmost_cnot, + ) # Very custom usage of Compute/CustomUncompute in the following. if rightmost_cnot[len(ucontrol_qubits)]: with Compute(eng): @@ -59,15 +58,16 @@ def _apply_ucr_n(angles, ucontrol_qubits, target_qubit, eng, gate_class, else: with CustomUncompute(eng): CNOT | (ucontrol_qubits[-1], target_qubit) - _apply_ucr_n(angles=angles2, - ucontrol_qubits=ucontrol_qubits[:-1], - target_qubit=target_qubit, - eng=eng, - gate_class=gate_class, - rightmost_cnot=rightmost_cnot) + _apply_ucr_n( + angles=angles2, + ucontrol_qubits=ucontrol_qubits[:-1], + target_qubit=target_qubit, + eng=eng, + gate_class=gate_class, + rightmost_cnot=rightmost_cnot, + ) # Next iteration on this level do the other cnot placement - rightmost_cnot[len(ucontrol_qubits)] = ( - not rightmost_cnot[len(ucontrol_qubits)]) + rightmost_cnot[len(ucontrol_qubits)] = not rightmost_cnot[len(ucontrol_qubits)] def _decompose_ucr(cmd, gate_class): @@ -90,36 +90,40 @@ def _decompose_ucr(cmd, gate_class): raise TypeError("Wrong number of qubits ") ucontrol_qubits = cmd.qubits[0] target_qubit = cmd.qubits[1] - if not len(cmd.gate.angles) == 2**len(ucontrol_qubits): + if not len(cmd.gate.angles) == 2 ** len(ucontrol_qubits): raise ValueError("Wrong len(angles).") if len(ucontrol_qubits) == 0: gate_class(cmd.gate.angles[0]) | target_qubit return angles1 = [] angles2 = [] - for lower_bits in range(2**(len(ucontrol_qubits)-1)): + for lower_bits in range(2 ** (len(ucontrol_qubits) - 1)): leading_0 = cmd.gate.angles[lower_bits] - leading_1 = cmd.gate.angles[lower_bits+2**(len(ucontrol_qubits)-1)] - angles1.append((leading_0 + leading_1)/2.) - angles2.append((leading_0 - leading_1)/2.) + leading_1 = cmd.gate.angles[lower_bits + 2 ** (len(ucontrol_qubits) - 1)] + angles1.append((leading_0 + leading_1) / 2.0) + angles2.append((leading_0 - leading_1) / 2.0) rightmost_cnot = {} for i in range(len(ucontrol_qubits) + 1): rightmost_cnot[i] = True - _apply_ucr_n(angles=angles1, - ucontrol_qubits=ucontrol_qubits[:-1], - target_qubit=target_qubit, - eng=eng, - gate_class=gate_class, - rightmost_cnot=rightmost_cnot) + _apply_ucr_n( + angles=angles1, + ucontrol_qubits=ucontrol_qubits[:-1], + target_qubit=target_qubit, + eng=eng, + gate_class=gate_class, + rightmost_cnot=rightmost_cnot, + ) # Very custom usage of Compute/CustomUncompute in the following. with Compute(cmd.engine): CNOT | (ucontrol_qubits[-1], target_qubit) - _apply_ucr_n(angles=angles2, - ucontrol_qubits=ucontrol_qubits[:-1], - target_qubit=target_qubit, - eng=eng, - gate_class=gate_class, - rightmost_cnot=rightmost_cnot) + _apply_ucr_n( + angles=angles2, + ucontrol_qubits=ucontrol_qubits[:-1], + target_qubit=target_qubit, + eng=eng, + gate_class=gate_class, + rightmost_cnot=rightmost_cnot, + ) with CustomUncompute(eng): CNOT | (ucontrol_qubits[-1], target_qubit) @@ -135,5 +139,5 @@ def _decompose_ucrz(cmd): #: Decomposition rules all_defined_decomposition_rules = [ DecompositionRule(UniformlyControlledRy, _decompose_ucry), - DecompositionRule(UniformlyControlledRz, _decompose_ucrz) + DecompositionRule(UniformlyControlledRz, _decompose_ucrz), ] diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py index 52c2555a9..7361d4292 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,20 +12,29 @@ # 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.uniformlycontrolledr2cnot.""" import pytest -import projectq from projectq import MainEngine from projectq.backends import Simulator -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, - InstructionFilter) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + DummyEngine, + InstructionFilter, +) from projectq.meta import Compute, Control, Uncompute -from projectq.ops import (All, Measure, Ry, Rz, UniformlyControlledRy, - UniformlyControlledRz, X) +from projectq.ops import ( + All, + Measure, + Ry, + Rz, + UniformlyControlledRy, + UniformlyControlledRz, + X, +) import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot @@ -34,8 +44,8 @@ def slow_implementation(angles, control_qubits, target_qubit, eng, gate_class): Assumption is that control_qubits[0] is lowest order bit We apply angles[0] to state |0> """ - assert len(angles) == 2**len(control_qubits) - for index in range(2**len(control_qubits)): + assert len(angles) == 2 ** len(control_qubits) + for index in range(2 ** len(control_qubits)): with Compute(eng): for bit_pos in range(len(control_qubits)): if not (index >> bit_pos) & 1: @@ -46,17 +56,17 @@ def slow_implementation(angles, control_qubits, target_qubit, eng, gate_class): def _decomp_gates(eng, cmd): - if (isinstance(cmd.gate, UniformlyControlledRy) or - isinstance(cmd.gate, UniformlyControlledRz)): + if isinstance(cmd.gate, UniformlyControlledRy) or isinstance(cmd.gate, UniformlyControlledRz): return False return True def test_no_control_qubits(): rule_set = DecompositionRuleSet(modules=[ucr2cnot]) - eng = MainEngine(backend=DummyEngine(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(_decomp_gates)]) + eng = MainEngine( + backend=DummyEngine(), + engine_list=[AutoReplacer(rule_set), InstructionFilter(_decomp_gates)], + ) qb = eng.allocate_qubit() with pytest.raises(TypeError): UniformlyControlledRy([0.1]) | qb @@ -64,33 +74,52 @@ def test_no_control_qubits(): def test_wrong_number_of_angles(): rule_set = DecompositionRuleSet(modules=[ucr2cnot]) - eng = MainEngine(backend=DummyEngine(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(_decomp_gates)]) + eng = MainEngine( + backend=DummyEngine(), + engine_list=[AutoReplacer(rule_set), InstructionFilter(_decomp_gates)], + ) qb = eng.allocate_qubit() with pytest.raises(ValueError): UniformlyControlledRy([0.1, 0.2]) | ([], qb) -@pytest.mark.parametrize("gate_classes", [(Ry, UniformlyControlledRy), - (Rz, UniformlyControlledRz)]) +@pytest.mark.parametrize("gate_classes", [(Ry, UniformlyControlledRy), (Rz, UniformlyControlledRz)]) @pytest.mark.parametrize("n", [0, 1, 2, 3, 4]) def test_uniformly_controlled_ry(n, gate_classes): - random_angles = [0.5, 0.8, 1.2, 2.5, 4.4, 2.32, 6.6, 15.12, 1, 2, 9.54, - 2.1, 3.1415, 1.1, 0.01, 0.99] - angles = random_angles[:2**n] - for basis_state_index in range(0, 2**(n+1)): - basis_state = [0] * 2**(n+1) - basis_state[basis_state_index] = 1. + random_angles = [ + 0.5, + 0.8, + 1.2, + 2.5, + 4.4, + 2.32, + 6.6, + 15.12, + 1, + 2, + 9.54, + 2.1, + 3.1415, + 1.1, + 0.01, + 0.99, + ] + angles = random_angles[: 2 ** n] + for basis_state_index in range(0, 2 ** (n + 1)): + basis_state = [0] * 2 ** (n + 1) + basis_state[basis_state_index] = 1.0 correct_dummy_eng = DummyEngine(save_commands=True) - correct_eng = MainEngine(backend=Simulator(), - engine_list=[correct_dummy_eng]) + correct_eng = MainEngine(backend=Simulator(), engine_list=[correct_dummy_eng]) rule_set = DecompositionRuleSet(modules=[ucr2cnot]) test_dummy_eng = DummyEngine(save_commands=True) - test_eng = MainEngine(backend=Simulator(), - engine_list=[AutoReplacer(rule_set), - InstructionFilter(_decomp_gates), - test_dummy_eng]) + 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() @@ -100,25 +129,24 @@ def test_uniformly_controlled_ry(n, gate_classes): test_ctrl_qureg = test_eng.allocate_qureg(n) test_eng.flush() - correct_sim.set_wavefunction(basis_state, correct_qb + - correct_ctrl_qureg) + correct_sim.set_wavefunction(basis_state, correct_qb + correct_ctrl_qureg) test_sim.set_wavefunction(basis_state, test_qb + test_ctrl_qureg) gate_classes[1](angles) | (test_ctrl_qureg, test_qb) - slow_implementation(angles=angles, - control_qubits=correct_ctrl_qureg, - target_qubit=correct_qb, - eng=correct_eng, - gate_class=gate_classes[0]) + slow_implementation( + angles=angles, + control_qubits=correct_ctrl_qureg, + target_qubit=correct_qb, + eng=correct_eng, + gate_class=gate_classes[0], + ) test_eng.flush() correct_eng.flush() - for fstate in range(2**(n+1)): - binary_state = format(fstate, '0' + str(n+1) + 'b') - test = test_sim.get_amplitude(binary_state, - test_qb + test_ctrl_qureg) - correct = correct_sim.get_amplitude(binary_state, correct_qb + - correct_ctrl_qureg) + for fstate in range(2 ** (n + 1)): + binary_state = format(fstate, '0' + str(n + 1) + 'b') + test = test_sim.get_amplitude(binary_state, test_qb + test_ctrl_qureg) + correct = correct_sim.get_amplitude(binary_state, correct_qb + correct_ctrl_qureg) assert correct == pytest.approx(test, rel=1e-10, abs=1e-10) All(Measure) | test_qb + test_ctrl_qureg diff --git a/projectq/setups/default.py b/projectq/setups/default.py index c3e9da9db..8f9edeb30 100755 --- a/projectq/setups/default.py +++ b/projectq/setups/default.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Defines the default setup which provides an `engine_list` for the `MainEngine` @@ -21,16 +21,20 @@ import projectq import projectq.setups.decompositions -from projectq.cengines import (TagRemover, - LocalOptimizer, - AutoReplacer, - DecompositionRuleSet) +from projectq.cengines import ( + TagRemover, + LocalOptimizer, + AutoReplacer, + DecompositionRuleSet, +) def get_engine_list(): rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - return [TagRemover(), - LocalOptimizer(10), - AutoReplacer(rule_set), - TagRemover(), - LocalOptimizer(10)] + return [ + TagRemover(), + LocalOptimizer(10), + AutoReplacer(rule_set), + TagRemover(), + LocalOptimizer(10), + ] diff --git a/projectq/setups/grid.py b/projectq/setups/grid.py index 674462fed..9204bd7ae 100644 --- a/projectq/setups/grid.py +++ b/projectq/setups/grid.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Defines a setup to compile to qubits placed in 2-D grid. @@ -28,11 +28,23 @@ import projectq import projectq.libs.math import projectq.setups.decompositions -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - InstructionFilter, GridMapper, - LocalOptimizer, TagRemover) -from projectq.ops import (BasicMathGate, ClassicalInstructionGate, CNOT, - ControlledGate, get_inverse, QFT, Swap) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + InstructionFilter, + GridMapper, + LocalOptimizer, + TagRemover, +) +from projectq.ops import ( + BasicMathGate, + ClassicalInstructionGate, + CNOT, + ControlledGate, + get_inverse, + QFT, + Swap, +) def high_level_gates(eng, cmd): @@ -58,8 +70,7 @@ def one_and_two_qubit_gates(eng, cmd): return False -def get_engine_list(num_rows, num_columns, one_qubit_gates="any", - two_qubit_gates=(CNOT, Swap)): +def get_engine_list(num_rows, num_columns, one_qubit_gates="any", two_qubit_gates=(CNOT, Swap)): """ Returns an engine list to compile to a 2-D grid of qubits. @@ -103,15 +114,16 @@ def get_engine_list(num_rows, num_columns, one_qubit_gates="any", A list of suitable compiler engines. """ if two_qubit_gates != "any" and not isinstance(two_qubit_gates, tuple): - raise TypeError("two_qubit_gates parameter must be 'any' or a tuple. " - "When supplying only one gate, make sure to correctly " - "create the tuple (don't miss the comma), " - "e.g. two_qubit_gates=(CNOT,)") + raise TypeError( + "two_qubit_gates parameter must be 'any' or a tuple. " + "When supplying only one gate, make sure to correctly " + "create the tuple (don't miss the comma), " + "e.g. two_qubit_gates=(CNOT,)" + ) if one_qubit_gates != "any" and not isinstance(one_qubit_gates, tuple): raise TypeError("one_qubit_gates parameter must be 'any' or a tuple.") - rule_set = DecompositionRuleSet(modules=[projectq.libs.math, - projectq.setups.decompositions]) + rule_set = DecompositionRuleSet(modules=[projectq.libs.math, projectq.setups.decompositions]) allowed_gate_classes = [] allowed_gate_instances = [] if one_qubit_gates != "any": @@ -152,17 +164,18 @@ def low_level_gates(eng, cmd): else: return False - return [AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(high_level_gates), - LocalOptimizer(5), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(one_and_two_qubit_gates), - LocalOptimizer(5), - GridMapper(num_rows=num_rows, num_columns=num_columns), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(low_level_gates), - LocalOptimizer(5), - ] + return [ + AutoReplacer(rule_set), + TagRemover(), + InstructionFilter(high_level_gates), + LocalOptimizer(5), + AutoReplacer(rule_set), + TagRemover(), + InstructionFilter(one_and_two_qubit_gates), + LocalOptimizer(5), + GridMapper(num_rows=num_rows, num_columns=num_columns), + AutoReplacer(rule_set), + TagRemover(), + InstructionFilter(low_level_gates), + LocalOptimizer(5), + ] diff --git a/projectq/setups/grid_test.py b/projectq/setups/grid_test.py index 13373570c..3794e33f0 100644 --- a/projectq/setups/grid_test.py +++ b/projectq/setups/grid_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.setups.squaregrid.""" import pytest @@ -37,9 +37,7 @@ def test_mapper_present_and_correct_params(): def test_parameter_any(): - engine_list = grid_setup.get_engine_list(num_rows=3, num_columns=2, - one_qubit_gates="any", - two_qubit_gates="any") + engine_list = grid_setup.get_engine_list(num_rows=3, num_columns=2, one_qubit_gates="any", two_qubit_gates="any") backend = DummyEngine(save_commands=True) eng = projectq.MainEngine(backend, engine_list) qubit1 = eng.allocate_qubit() @@ -54,10 +52,12 @@ def test_parameter_any(): def test_restriction(): - engine_list = grid_setup.get_engine_list(num_rows=3, num_columns=2, - one_qubit_gates=(Rz, H), - two_qubit_gates=(CNOT, - AddConstant)) + engine_list = grid_setup.get_engine_list( + num_rows=3, + num_columns=2, + one_qubit_gates=(Rz, H), + two_qubit_gates=(CNOT, AddConstant), + ) backend = DummyEngine(save_commands=True) eng = projectq.MainEngine(backend, engine_list) qubit1 = eng.allocate_qubit() @@ -85,12 +85,6 @@ def test_restriction(): def test_wrong_init(): with pytest.raises(TypeError): - engine_list = grid_setup.get_engine_list(num_rows=3, - num_columns=2, - one_qubit_gates="any", - two_qubit_gates=(CNOT)) + grid_setup.get_engine_list(num_rows=3, num_columns=2, one_qubit_gates="any", two_qubit_gates=(CNOT)) with pytest.raises(TypeError): - engine_list = grid_setup.get_engine_list(num_rows=3, - num_columns=2, - one_qubit_gates="Any", - two_qubit_gates=(CNOT,)) + grid_setup.get_engine_list(num_rows=3, num_columns=2, one_qubit_gates="Any", two_qubit_gates=(CNOT,)) diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index acedeed00..e1db71a9f 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,13 +23,15 @@ 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, Barrier) -from projectq.cengines import (LocalOptimizer, IBM5QubitMapper, - SwapAndCNOTFlipper, BasicMapperEngine, - GridMapper) +from projectq.ops import Rx, Ry, Rz, H, CNOT, Barrier +from projectq.cengines import ( + LocalOptimizer, + IBM5QubitMapper, + SwapAndCNOTFlipper, + BasicMapperEngine, + GridMapper, +) from projectq.backends._ibm._ibm_http_client import show_devices @@ -39,19 +42,14 @@ def get_engine_list(token=None, device=None): devices = show_devices(token) ibm_setup = [] if device not in devices: - raise DeviceOfflineError('Error when configuring engine list: device ' - 'requested for Backend not connected') + raise DeviceOfflineError('Error when configuring engine list: device requested for Backend not connected') 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 = list2set(coupling_map) mapper = IBM5QubitMapper(coupling_map) - ibm_setup = [ - mapper, - SwapAndCNOTFlipper(coupling_map), - LocalOptimizer(10) - ] + 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 @@ -84,7 +82,7 @@ def get_engine_list(token=None, device=None): 12: 10, 13: 9, 14: 8, - 15: 7 + 15: 7, } coupling_map = devices[device]['coupling_map'] coupling_map = list2set(coupling_map) @@ -92,7 +90,7 @@ def get_engine_list(token=None, device=None): GridMapper(2, 8, grid_to_physical), LocalOptimizer(5), SwapAndCNOTFlipper(coupling_map), - LocalOptimizer(5) + LocalOptimizer(5), ] else: # If there is an online device not handled into ProjectQ it's not too @@ -104,9 +102,9 @@ 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 diff --git a/projectq/setups/ibm_test.py b/projectq/setups/ibm_test.py index 26b41b24a..4b3bebc19 100644 --- a/projectq/setups/ibm_test.py +++ b/projectq/setups/ibm_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,32 +21,43 @@ 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)]) + 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 + 'nq': 5, }, 'ibmq_16_melbourne': { 'coupling_map': connections, 'version': '0.0.0', - 'nq': 15 + 'nq': 15, }, 'ibmq_qasm_simulator': { 'coupling_map': connections, 'version': '0.0.0', - 'nq': 32 - } + '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') + 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 @@ -55,15 +67,22 @@ 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): diff --git a/projectq/setups/linear.py b/projectq/setups/linear.py index 0f5c1d172..2f8c80e02 100644 --- a/projectq/setups/linear.py +++ b/projectq/setups/linear.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ Defines a setup to compile to qubits placed in a linear chain or a circle. @@ -28,11 +28,23 @@ import projectq import projectq.libs.math import projectq.setups.decompositions -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - InstructionFilter, LinearMapper, LocalOptimizer, - TagRemover) -from projectq.ops import (BasicMathGate, ClassicalInstructionGate, CNOT, - ControlledGate, get_inverse, QFT, Swap) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + InstructionFilter, + LinearMapper, + LocalOptimizer, + TagRemover, +) +from projectq.ops import ( + BasicMathGate, + ClassicalInstructionGate, + CNOT, + ControlledGate, + get_inverse, + QFT, + Swap, +) def high_level_gates(eng, cmd): @@ -58,8 +70,7 @@ def one_and_two_qubit_gates(eng, cmd): return False -def get_engine_list(num_qubits, cyclic=False, one_qubit_gates="any", - two_qubit_gates=(CNOT, Swap)): +def get_engine_list(num_qubits, cyclic=False, one_qubit_gates="any", two_qubit_gates=(CNOT, Swap)): """ Returns an engine list to compile to a linear chain of qubits. @@ -103,15 +114,16 @@ def get_engine_list(num_qubits, cyclic=False, one_qubit_gates="any", A list of suitable compiler engines. """ if two_qubit_gates != "any" and not isinstance(two_qubit_gates, tuple): - raise TypeError("two_qubit_gates parameter must be 'any' or a tuple. " - "When supplying only one gate, make sure to correctly " - "create the tuple (don't miss the comma), " - "e.g. two_qubit_gates=(CNOT,)") + raise TypeError( + "two_qubit_gates parameter must be 'any' or a tuple. " + "When supplying only one gate, make sure to correctly " + "create the tuple (don't miss the comma), " + "e.g. two_qubit_gates=(CNOT,)" + ) if one_qubit_gates != "any" and not isinstance(one_qubit_gates, tuple): raise TypeError("one_qubit_gates parameter must be 'any' or a tuple.") - rule_set = DecompositionRuleSet(modules=[projectq.libs.math, - projectq.setups.decompositions]) + rule_set = DecompositionRuleSet(modules=[projectq.libs.math, projectq.setups.decompositions]) allowed_gate_classes = [] allowed_gate_instances = [] if one_qubit_gates != "any": @@ -152,17 +164,18 @@ def low_level_gates(eng, cmd): else: return False - return [AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(high_level_gates), - LocalOptimizer(5), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(one_and_two_qubit_gates), - LocalOptimizer(5), - LinearMapper(num_qubits=num_qubits, cyclic=cyclic), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(low_level_gates), - LocalOptimizer(5), - ] + return [ + AutoReplacer(rule_set), + TagRemover(), + InstructionFilter(high_level_gates), + LocalOptimizer(5), + AutoReplacer(rule_set), + TagRemover(), + InstructionFilter(one_and_two_qubit_gates), + LocalOptimizer(5), + LinearMapper(num_qubits=num_qubits, cyclic=cyclic), + AutoReplacer(rule_set), + TagRemover(), + InstructionFilter(low_level_gates), + LocalOptimizer(5), + ] diff --git a/projectq/setups/linear_test.py b/projectq/setups/linear_test.py index ff162fcf5..45c480475 100644 --- a/projectq/setups/linear_test.py +++ b/projectq/setups/linear_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Tests for projectq.setups.linear.""" import pytest @@ -37,9 +37,9 @@ def test_mapper_present_and_correct_params(): def test_parameter_any(): - engine_list = linear_setup.get_engine_list(num_qubits=10, cyclic=False, - one_qubit_gates="any", - two_qubit_gates="any") + engine_list = linear_setup.get_engine_list( + num_qubits=10, cyclic=False, one_qubit_gates="any", two_qubit_gates="any" + ) backend = DummyEngine(save_commands=True) eng = projectq.MainEngine(backend, engine_list) qubit1 = eng.allocate_qubit() @@ -54,10 +54,12 @@ def test_parameter_any(): def test_restriction(): - engine_list = linear_setup.get_engine_list(num_qubits=10, cyclic=False, - one_qubit_gates=(Rz, H), - two_qubit_gates=(CNOT, - AddConstant)) + engine_list = linear_setup.get_engine_list( + num_qubits=10, + cyclic=False, + one_qubit_gates=(Rz, H), + two_qubit_gates=(CNOT, AddConstant), + ) backend = DummyEngine(save_commands=True) eng = projectq.MainEngine(backend, engine_list) qubit1 = eng.allocate_qubit() @@ -85,10 +87,6 @@ def test_restriction(): def test_wrong_init(): with pytest.raises(TypeError): - engine_list = linear_setup.get_engine_list(num_qubits=10, cyclic=False, - one_qubit_gates="any", - two_qubit_gates=(CNOT)) + linear_setup.get_engine_list(num_qubits=10, cyclic=False, one_qubit_gates="any", two_qubit_gates=(CNOT)) with pytest.raises(TypeError): - engine_list = linear_setup.get_engine_list(num_qubits=10, cyclic=False, - one_qubit_gates="Any", - two_qubit_gates=(CNOT,)) + linear_setup.get_engine_list(num_qubits=10, cyclic=False, one_qubit_gates="Any", two_qubit_gates=(CNOT,)) diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index fe0c00ba2..f12877bcb 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,10 +26,23 @@ import projectq import projectq.libs.math import projectq.setups.decompositions -from projectq.cengines import (AutoReplacer, DecompositionRuleSet, - InstructionFilter, LocalOptimizer, TagRemover) -from projectq.ops import (BasicGate, BasicMathGate, ClassicalInstructionGate, - CNOT, ControlledGate, get_inverse, QFT, Swap) +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + InstructionFilter, + LocalOptimizer, + TagRemover, +) +from projectq.ops import ( + BasicGate, + BasicMathGate, + ClassicalInstructionGate, + CNOT, + ControlledGate, + get_inverse, + QFT, + Swap, +) def high_level_gates(eng, cmd): @@ -62,10 +76,12 @@ 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): +def get_engine_list( + one_qubit_gates="any", + two_qubit_gates=(CNOT,), + other_gates=(), + compiler_chooser=default_chooser, +): """ Returns an engine list to compile to a restricted gate set. @@ -116,17 +132,18 @@ def get_engine_list(one_qubit_gates="any", A list of suitable compiler engines. """ if two_qubit_gates != "any" and not isinstance(two_qubit_gates, tuple): - raise TypeError("two_qubit_gates parameter must be 'any' or a tuple. " - "When supplying only one gate, make sure to correctly " - "create the tuple (don't miss the comma), " - "e.g. two_qubit_gates=(CNOT,)") + raise TypeError( + "two_qubit_gates parameter must be 'any' or a tuple. " + "When supplying only one gate, make sure to correctly " + "create the tuple (don't miss the comma), " + "e.g. two_qubit_gates=(CNOT,)" + ) if one_qubit_gates != "any" and not isinstance(one_qubit_gates, tuple): raise TypeError("one_qubit_gates parameter must be 'any' or a tuple.") if not isinstance(other_gates, tuple): raise TypeError("other_gates parameter must be a tuple.") - rule_set = DecompositionRuleSet( - modules=[projectq.libs.math, projectq.setups.decompositions]) + rule_set = DecompositionRuleSet(modules=[projectq.libs.math, projectq.setups.decompositions]) allowed_gate_classes = [] # n-qubit gates allowed_gate_instances = [] allowed_gate_classes1 = [] # 1-qubit gates @@ -189,16 +206,13 @@ def low_level_gates(eng, cmd): return True elif (cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances: return True - elif (isinstance(cmd.gate, allowed_gate_classes1) - and len(all_qubits) == 1): + elif isinstance(cmd.gate, allowed_gate_classes1) and len(all_qubits) == 1: return True - elif (isinstance(cmd.gate, allowed_gate_classes2) - and len(all_qubits) == 2): + elif isinstance(cmd.gate, allowed_gate_classes2) and len(all_qubits) == 2: return True elif cmd.gate in allowed_gate_instances1 and len(all_qubits) == 1: return True - elif ((cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances2 - and len(all_qubits) == 2): + elif (cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances2 and len(all_qubits) == 2: return True return False diff --git a/projectq/setups/restrictedgateset_test.py b/projectq/setups/restrictedgateset_test.py index fe9754aa7..bf2a7c8b4 100644 --- a/projectq/setups/restrictedgateset_test.py +++ b/projectq/setups/restrictedgateset_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,18 +18,29 @@ import projectq from projectq.cengines import DummyEngine -from projectq.libs.math import (AddConstant, AddConstantModN, - MultiplyByConstantModN) -from projectq.ops import (BasicGate, CNOT, CRz, H, Measure, QFT, QubitOperator, - Rx, Rz, Swap, TimeEvolution, Toffoli, X) +from projectq.libs.math import AddConstant, AddConstantModN, MultiplyByConstantModN +from projectq.ops import ( + BasicGate, + CNOT, + CRz, + H, + Measure, + QFT, + QubitOperator, + Rx, + Rz, + Swap, + TimeEvolution, + Toffoli, + X, +) from projectq.meta import Control import projectq.setups.restrictedgateset as restrictedgateset def test_parameter_any(): - engine_list = restrictedgateset.get_engine_list(one_qubit_gates="any", - two_qubit_gates="any") + engine_list = restrictedgateset.get_engine_list(one_qubit_gates="any", two_qubit_gates="any") backend = DummyEngine(save_commands=True) eng = projectq.MainEngine(backend, engine_list) qubit1 = eng.allocate_qubit() @@ -46,7 +58,8 @@ def test_restriction(): engine_list = restrictedgateset.get_engine_list( one_qubit_gates=(Rz, H), two_qubit_gates=(CNOT, AddConstant, Swap), - other_gates=(Toffoli, AddConstantModN, MultiplyByConstantModN(2, 8))) + other_gates=(Toffoli, AddConstantModN, MultiplyByConstantModN(2, 8)), + ) backend = DummyEngine(save_commands=True) eng = projectq.MainEngine(backend, engine_list, verbose=True) qubit1 = eng.allocate_qubit() @@ -94,8 +107,8 @@ def test_wrong_init(): with pytest.raises(TypeError): restrictedgateset.get_engine_list(other_gates="any") with pytest.raises(TypeError): - restrictedgateset.get_engine_list(one_qubit_gates=(CRz, )) + restrictedgateset.get_engine_list(one_qubit_gates=(CRz,)) with pytest.raises(TypeError): - restrictedgateset.get_engine_list(two_qubit_gates=(CRz, )) + restrictedgateset.get_engine_list(two_qubit_gates=(CRz,)) with pytest.raises(TypeError): - restrictedgateset.get_engine_list(other_gates=(CRz, )) + restrictedgateset.get_engine_list(other_gates=(CRz,)) diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index f5d19f1c8..158073f93 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +34,7 @@ """ from projectq.setups import restrictedgateset -from projectq.ops import (Rxx, Rx, Ry) +from projectq.ops import Rxx, Rx, Ry from projectq.meta import get_control_count # ------------------chooser_Ry_reducer-------------------# @@ -144,5 +145,6 @@ def get_engine_list(): """ return restrictedgateset.get_engine_list( one_qubit_gates=(Rx, Ry), - two_qubit_gates=(Rxx, ), - compiler_chooser=chooser_Ry_reducer) + 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 index 23b6485c6..7aaea0ff3 100644 --- a/projectq/setups/trapped_ion_decomposer_test.py +++ b/projectq/setups/trapped_ion_decomposer_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2018 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,11 +16,16 @@ "Tests for projectq.setups.trapped_ion_decomposer.py." import projectq -from projectq.ops import (Rx, Ry, Rz, H, X, CNOT, Measure, Rxx, - ClassicalInstructionGate) -from projectq.cengines import (MainEngine, DummyEngine, AutoReplacer, - TagRemover, InstructionFilter, - DecompositionRuleSet, DecompositionRule) +from projectq.ops import Rx, Ry, Rz, H, X, CNOT, Measure, Rxx, ClassicalInstructionGate +from projectq.cengines import ( + MainEngine, + DummyEngine, + AutoReplacer, + TagRemover, + InstructionFilter, + DecompositionRuleSet, + DecompositionRule, +) from projectq.meta import get_control_count from . import restrictedgateset @@ -29,16 +35,14 @@ def filter_gates(eng, cmd): if isinstance(cmd.gate, ClassicalInstructionGate): return True - if ((cmd.gate == X and get_control_count(cmd) == 1) or cmd.gate == H - or isinstance(cmd.gate, Rz)): + if (cmd.gate == X and get_control_count(cmd) == 1) or cmd.gate == H or isinstance(cmd.gate, Rz): return False return True def test_chooser_Ry_reducer_synthetic(): backend = DummyEngine(save_commands=True) - rule_set = DecompositionRuleSet( - modules=[projectq.libs.math, projectq.setups.decompositions]) + rule_set = DecompositionRuleSet(modules=[projectq.libs.math, projectq.setups.decompositions]) engine_list = [ AutoReplacer(rule_set, chooser_Ry_reducer), @@ -59,8 +63,7 @@ def test_chooser_Ry_reducer_synthetic(): assert isinstance(backend.received_commands[idx0].gate, Ry) assert isinstance(backend.received_commands[idx1].gate, Ry) - assert (backend.received_commands[idx0].gate.get_inverse() == - backend.received_commands[idx1].gate) + assert backend.received_commands[idx0].gate.get_inverse() == backend.received_commands[idx1].gate eng = MainEngine(backend=backend, engine_list=engine_list) control = eng.allocate_qubit() @@ -74,8 +77,7 @@ def test_chooser_Ry_reducer_synthetic(): assert isinstance(backend.received_commands[idx0].gate, Ry) assert isinstance(backend.received_commands[idx1].gate, Ry) - assert (backend.received_commands[idx0].gate.get_inverse() == - backend.received_commands[idx1].gate) + assert backend.received_commands[idx0].gate.get_inverse() == backend.received_commands[idx1].gate eng = MainEngine(backend=backend, engine_list=engine_list) control = eng.allocate_qubit() @@ -89,8 +91,7 @@ def test_chooser_Ry_reducer_synthetic(): assert isinstance(backend.received_commands[idx0].gate, Ry) assert isinstance(backend.received_commands[idx1].gate, Ry) - assert (backend.received_commands[idx0].gate.get_inverse() == - backend.received_commands[idx1].gate) + assert backend.received_commands[idx0].gate.get_inverse() == backend.received_commands[idx1].gate def _dummy_h2nothing_A(cmd): @@ -100,8 +101,7 @@ def _dummy_h2nothing_A(cmd): def test_chooser_Ry_reducer_unsupported_gate(): backend = DummyEngine(save_commands=True) - rule_set = DecompositionRuleSet( - rules=[DecompositionRule(H.__class__, _dummy_h2nothing_A)]) + rule_set = DecompositionRuleSet(rules=[DecompositionRule(H.__class__, _dummy_h2nothing_A)]) engine_list = [ AutoReplacer(rule_set, chooser_Ry_reducer), @@ -132,10 +132,13 @@ def test_chooser_Ry_reducer(): # Using the chooser_Rx_reducer you get 10 commands, since you now have 4 # single qubit gates and 1 two qubit gate. - for engine_list, count in [(restrictedgateset.get_engine_list( - one_qubit_gates=(Rx, Ry), - two_qubit_gates=(Rxx, )), 13), - (get_engine_list(), 11)]: + for engine_list, count in [ + ( + restrictedgateset.get_engine_list(one_qubit_gates=(Rx, Ry), two_qubit_gates=(Rxx,)), + 13, + ), + (get_engine_list(), 11), + ]: backend = DummyEngine(save_commands=True) eng = projectq.MainEngine(backend, engine_list, verbose=True) diff --git a/projectq/tests/__init__.py b/projectq/tests/__init__.py index ee1451dcd..16fc4afdf 100755 --- a/projectq/tests/__init__.py +++ b/projectq/tests/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/tests/_factoring_test.py b/projectq/tests/_factoring_test.py index 7e9135911..88ee0db4a 100755 --- a/projectq/tests/_factoring_test.py +++ b/projectq/tests/_factoring_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,19 +18,19 @@ import projectq.libs.math import projectq.setups.decompositions from projectq.backends._sim._simulator_test import sim -from projectq.cengines import (MainEngine, - AutoReplacer, - DecompositionRuleSet, - InstructionFilter, - LocalOptimizer, - TagRemover) +from projectq.cengines import ( + MainEngine, + AutoReplacer, + DecompositionRuleSet, + InstructionFilter, + LocalOptimizer, + TagRemover, +) from projectq.libs.math import MultiplyByConstantModN from projectq.meta import Control -from projectq.ops import (All, BasicMathGate, get_inverse, H, Measure, QFT, - Swap, X) +from projectq.ops import All, BasicMathGate, get_inverse, H, Measure, QFT, Swap, X -rule_set = DecompositionRuleSet(modules=(projectq.libs.math, - projectq.setups.decompositions)) +rule_set = DecompositionRuleSet(modules=(projectq.libs.math, projectq.setups.decompositions)) assert sim # Asserts to tools that the fixture import is used. @@ -44,13 +45,15 @@ def high_level_gates(eng, cmd): def get_main_engine(sim): - engine_list = [AutoReplacer(rule_set), - InstructionFilter(high_level_gates), - TagRemover(), - LocalOptimizer(3), - AutoReplacer(rule_set), - TagRemover(), - LocalOptimizer(3)] + engine_list = [ + AutoReplacer(rule_set), + InstructionFilter(high_level_gates), + TagRemover(), + LocalOptimizer(3), + AutoReplacer(rule_set), + TagRemover(), + LocalOptimizer(3), + ] return MainEngine(sim, engine_list) @@ -67,7 +70,7 @@ def test_factoring(sim): H | ctrl_qubit with Control(eng, ctrl_qubit): - MultiplyByConstantModN(pow(a, 2**7, N), N) | x + MultiplyByConstantModN(pow(a, 2 ** 7, N), N) | x H | ctrl_qubit eng.flush() @@ -76,7 +79,7 @@ def test_factoring(sim): vec = cheat_tpl[1] for i in range(len(vec)): - if abs(vec[i]) > 1.e-8: + if abs(vec[i]) > 1.0e-8: assert ((i >> idx) & 1) == 0 Measure | ctrl_qubit @@ -93,13 +96,13 @@ def test_factoring(sim): idx = cheat_tpl[0][ctrl_qubit[0].id] vec = cheat_tpl[1] - probability = 0. + probability = 0.0 for i in range(len(vec)): - if abs(vec[i]) > 1.e-8: + if abs(vec[i]) > 1.0e-8: if ((i >> idx) & 1) == 0: - probability += abs(vec[i])**2 + probability += abs(vec[i]) ** 2 - assert probability == pytest.approx(.5) + assert probability == pytest.approx(0.5) Measure | ctrl_qubit All(Measure) | x diff --git a/projectq/types/__init__.py b/projectq/types/__init__.py index 1abaea68b..96f7ecf24 100755 --- a/projectq/types/__init__.py +++ b/projectq/types/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projectq/types/_qubit.py b/projectq/types/_qubit.py index 19a98b716..96ffc2fe9 100755 --- a/projectq/types/_qubit.py +++ b/projectq/types/_qubit.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """ This file defines BasicQubit, Qubit, WeakQubit and Qureg. @@ -41,6 +41,7 @@ class BasicQubit(object): They have an id and a reference to the owning engine. """ + def __init__(self, engine, idx): """ Initialize a BasicQubit object. @@ -87,9 +88,7 @@ def __eq__(self, other): """ if self.id == -1: return self is other - return (isinstance(other, BasicQubit) and - self.id == other.id and - self.engine == other.engine) + return isinstance(other, BasicQubit) and self.id == other.id and self.engine == other.engine def __ne__(self, other): return not self.__eq__(other) @@ -118,6 +117,7 @@ class Qubit(BasicQubit): Thus the qubit is not copyable; only returns a reference to the same object. """ + def __del__(self): """ Destroy the qubit and deallocate it (automatically). @@ -165,6 +165,7 @@ class WeakQubitRef(BasicQubit): garbage-collected (and, thus, cleaned up early). Otherwise there is no difference between a WeakQubitRef and a Qubit object. """ + pass @@ -176,6 +177,7 @@ class Qureg(list): access necessary) and enables pretty-printing of general quantum registers (call Qureg.__str__(qureg)). """ + def __bool__(self): """ Return measured value if Qureg consists of 1 qubit only. @@ -187,8 +189,9 @@ def __bool__(self): if len(self) == 1: return bool(self[0]) else: - raise Exception("__bool__(qureg): Quantum register contains more " - "than 1 qubit. Use __bool__(qureg[idx]) instead.") + raise Exception( + "__bool__(qureg): Quantum register contains more than 1 qubit. Use __bool__(qureg[idx]) instead." + ) def __int__(self): """ @@ -201,8 +204,9 @@ def __int__(self): if len(self) == 1: return int(self[0]) else: - raise Exception("__int__(qureg): Quantum register contains more " - "than 1 qubit. Use __bool__(qureg[idx]) instead.") + raise Exception( + "__int__(qureg): Quantum register contains more than 1 qubit. Use __bool__(qureg[idx]) instead." + ) def __nonzero__(self): """ @@ -232,9 +236,7 @@ def __str__(self): count += 1 continue - out_list.append('{}-{}'.format(start_id, start_id + count - 1) - if count > 1 - else '{}'.format(start_id)) + out_list.append('{}-{}'.format(start_id, start_id + count - 1) if count > 1 else '{}'.format(start_id)) start_id = qubit_id count = 1 diff --git a/projectq/types/_qubit_test.py b/projectq/types/_qubit_test.py index 7de66c9c0..dfcad755c 100755 --- a/projectq/types/_qubit_test.py +++ b/projectq/types/_qubit_test.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +12,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.types._qubits.""" from copy import copy, deepcopy @@ -75,8 +75,7 @@ def test_basic_qubit_hash(): assert a == c and hash(a) == hash(c) # For performance reasons, low ids should not collide. - assert len(set(hash(_qubit.BasicQubit(fake_engine, e)) - for e in range(100))) == 100 + assert len(set(hash(_qubit.BasicQubit(fake_engine, e)) for e in range(100))) == 100 # Important that weakref.WeakSet in projectq.cengines._main.py works. # When id is -1, expect reference equality. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..c7034d6fe --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,85 @@ +[build-system] +requires = ["setuptools>=42", "wheel", "pybind11>=2", "setuptools_scm[toml]>=3.4"] +build-backend = "setuptools.build_meta" + +# ============================================================================== + +[tool.black] + + line-length = 120 + target-version = ['py36','py37','py38'] + skip-string-normalization = true + + +[tool.check-manifest] + ignore = [ + 'PKG-INFO', + '*.egg-info', + '*.egg-info/*', + 'setup.cfg', + '.hgtags', + '.hgsigs', + '.hgignore', + '.gitignore', + '.bzrignore', + '.gitattributes', + '.github/*', + '.travis.yml', + 'Jenkinsfile', + '*.mo', + '.clang-format', + '.gitmodules', + 'VERSION.txt', + '.editorconfig', + '*.yml', + '*.yaml', + 'docs/*', + 'docs/images/*', + 'examples/*', + ] + + + +[tool.coverage] + [tool.coverage.run] + omit = [ + '*_test.py', + '*_fixtures.py' + ] + + +[tool.pylint] + [tools.pylint.master] + ignore-patterns = [ + '__init__.py' + ] + + [tools.pylint.format] + max-line-length = 120 + + [tools.pylint.reports] + msg-template = '{path}:{line}: [{msg_id}, {obj}] {msg} ({symbol})' + + [tools.pylint.messages_control] + disable = [ + 'invalid-name', + 'expression-not-assigned', + 'pointless-statemen', + ] + + +[tool.pytest.ini_options] + +minversion = '6.0' +addopts = '-pno:warnings' +testpaths = ['projectq'] + +[tool.setuptools_scm] + +write_to = 'VERSION.txt' +write_to_template = '{version}' +local_scheme = 'no-local-version' + +[tool.yapf] + +column_limit = 120 diff --git a/pytest.ini b/pytest.ini deleted file mode 100755 index 9dd4e3a91..000000000 --- a/pytest.ini +++ /dev/null @@ -1,9 +0,0 @@ -[pytest] -testpaths = projectq - -filterwarnings = - error - ignore:the matrix subclass is not the recommended way:PendingDeprecationWarning - ignore:Matplotlib is currently using agg, which is a non-GUI backend, so cannot show the figure. - ignore:the imp module is deprecated in favour of importlib - ignore::pytest.PytestUnraisableExceptionWarning \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100755 index 60d6b013c..000000000 --- a/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -numpy -future -pytest>=3.1 -pybind11>=2.2.3 -requests -scipy -networkx -matplotlib>=2.2.3 diff --git a/requirements_tests.txt b/requirements_tests.txt new file mode 100644 index 000000000..ab10bc7de --- /dev/null +++ b/requirements_tests.txt @@ -0,0 +1,3 @@ +flaky +pytest >= 6.0 +pytest-cov diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..3d0ebb9c7 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,58 @@ +[metadata] + +name = projectq +version = file:VERSION.txt +url = http://www.projectq.ch +author = ProjectQ +author_email = info@projectq.ch +project_urls = + Documentation = https://projectq.readthedocs.io/en/latest/ + Issue Tracker = https://github.com/ProjectQ-Framework/ProjectQ/ +license = Apache License Version 2.0 +license_file = LICENSE +description = ProjectQ - An open source software framework for quantum computing +long_description = file:README.rst +long_description_content_type = text/x-rst; charset=UTF-8 +home_page = http://www.projectq.ch +requires_dist = setuptools +classifier = + License :: OSI Approved :: Apache Software License + Topic :: Software Development :: Libraries :: Python Modules + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + +[options] + +zip_safe = False +packages = find: +python_requires = >= 3 +install_requires = + matplotlib >= 2.2.3 + networkx >= 2 + numpy + requests + scipy + +[options.extras_require] + +braket = boto3 + +# ============================================================================== + +[flake8] + +max-line-length = 120 +exclude = + .git, + __pycache__, + docs/conf.py, + build, + dist, + __init__.py +docstring-quotes = """ + +# ============================================================================== diff --git a/setup.py b/setup.py index ebd0b7ee3..9be796a62 100755 --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Some of the setup.py code is inspired or copied from SQLAlchemy # SQLAlchemy was created by Michael Bayer. @@ -36,9 +37,17 @@ # IN THE SOFTWARE. from __future__ import print_function -from setuptools import setup, Extension, find_packages -from distutils.errors import (CompileError, LinkError, CCompilerError, - DistutilsExecError, DistutilsPlatformError) +from setuptools import setup, Extension +import distutils.log +from distutils.cmd import Command +from distutils.spawn import find_executable, spawn +from distutils.errors import ( + CompileError, + LinkError, + CCompilerError, + DistutilsExecError, + DistutilsPlatformError, +) from setuptools import Distribution as _Distribution from setuptools.command.build_ext import build_ext import sys @@ -51,16 +60,18 @@ class get_pybind_include(object): - '''Helper class to determine the pybind11 include path + """Helper class to determine the pybind11 include path The purpose of this class is to postpone importing pybind11 until it is actually installed, so that the ``get_include()`` - method can be invoked. ''' + method can be invoked.""" + def __init__(self, user=False): self.user = user def __str__(self): import pybind11 + return pybind11.get_include(self.user) @@ -78,20 +89,15 @@ def status_msgs(*msgs): print('-' * 75) -def compiler_test(compiler, - flagname=None, - link=False, - include='', - body='', - postargs=None): - ''' +def compiler_test(compiler, flagname=None, link=False, include='', body='', postargs=None): + """ Return a boolean indicating whether a flag name is supported on the specified compiler. - ''' + """ import tempfile + f = tempfile.NamedTemporaryFile('w', suffix='.cpp', delete=False) - f.write('{}\nint main (int argc, char **argv) {{ {} return 0; }}'.format( - include, body)) + f.write('{}\nint main (int argc, char **argv) {{ {} return 0; }}'.format(include, body)) f.close() ret = True @@ -112,9 +118,7 @@ def compiler_test(compiler, if not os.path.exists(obj_file[0]): raise RuntimeError('') if link: - compiler.link_executable(obj_file, - exec_name, - extra_postargs=postargs) + compiler.link_executable(obj_file, exec_name, extra_postargs=postargs) if compiler.compiler_type == 'msvc': err.close() @@ -130,25 +134,20 @@ def compiler_test(compiler, def _fix_macosx_header_paths(*args): # Fix path to SDK headers if necessary - _MACOSX_XCODE_REF_PATH = ('/Applications/Xcode.app/Contents/' - + 'Developer/Platforms/MacOSX.platform/' - + 'Developer') + _MACOSX_XCODE_REF_PATH = '/Applications/Xcode.app/Contents/' + 'Developer/Platforms/MacOSX.platform/' + 'Developer' _MACOSX_DEVTOOLS_REF_PATH = '/Library/Developer/CommandLineTools/' _has_xcode = os.path.exists(_MACOSX_XCODE_REF_PATH) _has_devtools = os.path.exists(_MACOSX_DEVTOOLS_REF_PATH) if not _has_xcode and not _has_devtools: - important_msgs('ERROR: Must install either Xcode or ' - + 'CommandLineTools!') + important_msgs('ERROR: Must install either Xcode or CommandLineTools!') raise BuildFailed() def _do_replace(idx, item): if not _has_xcode and _MACOSX_XCODE_REF_PATH in item: - compiler_args[idx] = item.replace(_MACOSX_XCODE_REF_PATH, - _MACOSX_DEVTOOLS_REF_PATH) + compiler_args[idx] = item.replace(_MACOSX_XCODE_REF_PATH, _MACOSX_DEVTOOLS_REF_PATH) if not _has_devtools and _MACOSX_DEVTOOLS_REF_PATH in item: - compiler_args[idx] = item.replace(_MACOSX_DEVTOOLS_REF_PATH, - _MACOSX_XCODE_REF_PATH) + compiler_args[idx] = item.replace(_MACOSX_DEVTOOLS_REF_PATH, _MACOSX_XCODE_REF_PATH) for compiler_args in args: for idx, item in enumerate(compiler_args): @@ -171,22 +170,9 @@ def __init__(self): if sys.platform == 'win32': # 2.6's distutils.msvc9compiler can raise an IOError when failing to # find the compiler - ext_errors += (IOError, ) + ext_errors += (IOError,) # ============================================================================== - -# This reads the __version__ variable from projectq/_version.py -exec(open('projectq/_version.py').read()) - -# Readme file as long_description: -long_description = open('README.rst').read() - -# Read in requirements.txt -with open('requirements.txt', 'r') as f_requirements: - requirements = f_requirements.readlines() -requirements = [r.strip() for r in requirements] - -# ------------------------------------------------------------------------------ # ProjectQ C++ extensions ext_modules = [ @@ -196,9 +182,10 @@ def __init__(self): include_dirs=[ # Path to pybind11 headers get_pybind_include(), - get_pybind_include(user=True) + get_pybind_include(user=True), ], - language='c++'), + language='c++', + ), ] # ============================================================================== @@ -206,11 +193,31 @@ def __init__(self): class BuildExt(build_ext): '''A custom build extension for adding compiler-specific options.''' + c_opts = { 'msvc': ['/EHsc'], 'unix': [], } + user_options = build_ext.user_options + [ + ( + 'gen-compiledb', + None, + 'Generate a compile_commands.json alongside the compilation ' 'implies (-n/--dry-run)', + ), + ] + + boolean_options = build_ext.boolean_options + ['gen-compiledb'] + + def initialize_options(self): + build_ext.initialize_options(self) + self.gen_compiledb = None + + def finalize_options(self): + build_ext.finalize_options(self) + if self.gen_compiledb: + self.dry_run = True + def run(self): try: build_ext.run(self) @@ -219,9 +226,32 @@ def run(self): def build_extensions(self): self._configure_compiler() + for ext in self.extensions: ext.extra_compile_args = self.opts ext.extra_link_args = self.link_opts + + if self.compiler.compiler_type == 'unix' and self.gen_compiledb: + compile_commands = [] + for ext in self.extensions: + commands = self._get_compilation_commands(ext) + for cmd, src in commands: + compile_commands.append( + { + 'directory': os.path.dirname(os.path.abspath(__file__)), + 'command': cmd, + 'file': os.path.abspath(src), + } + ) + + import json + + with open( + os.path.join(os.path.dirname(os.path.abspath(__file__)), 'compile_commands.json'), + 'w', + ) as fd: + json.dump(compile_commands, fd, sort_keys=True, indent=4) + try: build_ext.build_extensions(self) except ext_errors: @@ -232,10 +262,51 @@ def build_extensions(self): raise BuildFailed() raise + def _get_compilation_commands(self, ext): + (macros, objects, extra_postargs, pp_opts, build,) = self.compiler._setup_compile( + outdir=self.build_temp, + sources=ext.sources, + macros=ext.define_macros, + incdirs=ext.include_dirs, + extra=ext.extra_compile_args, + depends=ext.depends, + ) + + cc_args = self.compiler._get_cc_args(pp_opts=pp_opts, debug=self.debug, before=None) + compiler_so = self.compiler.compiler_so + compiler_so[0] = find_executable(compiler_so[0]) + + commands = [] + for obj in objects: + try: + src, ext = build[obj] + except KeyError: + continue + + commands.append( + ( + ' '.join( + compiler_so + cc_args + [os.path.abspath(src), "-o", os.path.abspath(obj)] + extra_postargs + ), + src, + ) + ) + return commands + def _configure_compiler(self): + # Force dry_run = False to allow for compiler feature testing + dry_run_old = self.compiler.dry_run + self.compiler.dry_run = False + + if ( + int(os.environ.get('PROJECTQ_CLEANUP_COMPILER_FLAGS', 0)) + and self.compiler.compiler_type == 'unix' + and sys.platform != 'darwin' + ): + self._cleanup_compiler_flags() + if sys.platform == 'darwin': - _fix_macosx_header_paths(self.compiler.compiler, - self.compiler.compiler_so) + _fix_macosx_header_paths(self.compiler.compiler, self.compiler.compiler_so) if compiler_test(self.compiler, '-stdlib=libc++'): self.c_opts['unix'] += ['-stdlib=libc++'] @@ -246,8 +317,8 @@ def _configure_compiler(self): if not compiler_test(self.compiler): important_msgs( - 'ERROR: something is wrong with your C++ compiler.\n' - 'Failed to compile a simple test program!') + 'ERROR: something is wrong with your C++ compiler.\nFailed to compile a simple test program!' + ) raise BuildFailed() # ------------------------------ @@ -263,15 +334,11 @@ def _configure_compiler(self): # Other compiler tests status_msgs('Other compiler tests') - if ct == 'unix': - if compiler_test(self.compiler, '-fvisibility=hidden'): - self.opts.append('-fvisibility=hidden') - self.opts.append("-DVERSION_INFO=\"{}\"".format( - self.distribution.get_version())) - elif ct == 'msvc': - self.opts.append("/DVERSION_INFO=\\'{}\\'".format( - self.distribution.get_version())) + self.compiler.define_macro('VERSION_INFO', '"{}"'.format(self.distribution.get_version())) + if ct == 'unix' and compiler_test(self.compiler, '-fvisibility=hidden'): + self.opts.append('-fvisibility=hidden') + self.compiler.dry_run = dry_run_old status_msgs('Finished configuring compiler!') def _configure_openmp(self): @@ -281,7 +348,7 @@ def _configure_openmp(self): kwargs = { 'link': True, 'include': '#include ', - 'body': 'int a = omp_get_num_threads(); ++a;' + 'body': 'int a = omp_get_num_threads(); ++a;', } for flag in ['-openmp', '-fopenmp', '-qopenmp', '/Qopenmp']: @@ -291,21 +358,16 @@ def _configure_openmp(self): return flag = '-fopenmp' - if (sys.platform == 'darwin' and compiler_test(self.compiler, flag)): + if sys.platform == 'darwin' and compiler_test(self.compiler, flag): try: - llvm_root = subprocess.check_output( - ['brew', '--prefix', 'llvm']).decode('utf-8')[:-1] - compiler_root = subprocess.check_output( - ['which', self.compiler.compiler[0]]).decode('utf-8')[:-1] + llvm_root = subprocess.check_output(['brew', '--prefix', 'llvm']).decode('utf-8')[:-1] + compiler_root = subprocess.check_output(['which', self.compiler.compiler[0]]).decode('utf-8')[:-1] # Only add the flag if the compiler we are using is the one # from HomeBrew if llvm_root in compiler_root: l_arg = '-L{}/lib'.format(llvm_root) - if compiler_test(self.compiler, - flag, - postargs=[l_arg], - **kwargs): + if compiler_test(self.compiler, flag, postargs=[l_arg], **kwargs): self.opts.append(flag) self.link_opts.extend((l_arg, flag)) return @@ -314,24 +376,21 @@ def _configure_openmp(self): try: # Only relevant for MacPorts users with clang-3.7 - port_path = subprocess.check_output(['which', 'port' - ]).decode('utf-8')[:-1] + port_path = subprocess.check_output(['which', 'port']).decode('utf-8')[:-1] macports_root = os.path.dirname(os.path.dirname(port_path)) - compiler_root = subprocess.check_output( - ['which', self.compiler.compiler[0]]).decode('utf-8')[:-1] + compiler_root = subprocess.check_output(['which', self.compiler.compiler[0]]).decode('utf-8')[:-1] # Only add the flag if the compiler we are using is the one # from MacPorts if macports_root in compiler_root: - c_arg = '-I{}/include/libomp'.format(macports_root) - l_arg = '-L{}/lib/libomp'.format(macports_root) - - if compiler_test(self.compiler, - flag, - postargs=[c_arg, l_arg], - **kwargs): - self.opts.extend((c_arg, flag)) - self.link_opts.extend((l_arg, flag)) + inc_dir = '{}/include/libomp'.format(macports_root) + lib_dir = '{}/lib/libomp'.format(macports_root) + c_arg = '-I' + inc_dir + l_arg = '-L' + lib_dir + + if compiler_test(self.compiler, flag, postargs=[c_arg, l_arg], **kwargs): + self.compiler.add_include_dir(inc_dir) + self.compiler.add_library_dir(lib_dir) return except subprocess.CalledProcessError: pass @@ -340,20 +399,21 @@ def _configure_openmp(self): def _configure_intrinsics(self): for flag in [ - '-march=native', '-mavx2', '/arch:AVX2', '/arch:CORE-AVX2', - '/arch:AVX' + '-march=native', + '-mavx2', + '/arch:AVX2', + '/arch:CORE-AVX2', + '/arch:AVX', ]: if compiler_test( - self.compiler, - flagname=flag, - link=False, - include='#include ', - body='__m256d neg = _mm256_set1_pd(1.0); (void)neg;'): - - if sys.platform == 'win32': - self.opts.extend(('/DINTRIN', flag)) - else: - self.opts.extend(('-DINTRIN', flag)) + self.compiler, + flagname=flag, + link=False, + include='#include ', + body='__m256d neg = _mm256_set1_pd(1.0); (void)neg;', + ): + self.opts.append(flag) + self.compiler.define_macro("INTRIN") break for flag in ['-ffast-math', '-fast', '/fast', '/fp:precise']: @@ -388,6 +448,112 @@ def _configure_cxx_standard(self): important_msgs('ERROR: compiler needs to have at least C++11 support!') raise BuildFailed() + def _cleanup_compiler_flags(self): + compiler = self.compiler.compiler[0] + compiler_so = self.compiler.compiler_so[0] + linker_so = self.compiler.linker_so[0] + compiler_flags = set(self.compiler.compiler[1:]) + compiler_so_flags = set(self.compiler.compiler_so[1:]) + linker_so_flags = set(self.compiler.linker_so[1:]) + common_flags = compiler_flags & compiler_so_flags & linker_so_flags + + self.compiler.compiler = [compiler] + list(compiler_flags - common_flags) + self.compiler.compiler_so = [compiler_so] + list(compiler_so_flags - common_flags) + self.compiler.linker_so = [linker_so] + list(linker_so_flags - common_flags) + + flags = [] + for flag in common_flags: + if compiler_test(self.compiler, flag): + flags.append(flag) + else: + important_msgs('WARNING: ignoring unsupported compiler flag: {}'.format(flag)) + + self.compiler.compiler.extend(flags) + self.compiler.compiler_so.extend(flags) + self.compiler.linker_so.extend(flags) + + +# ------------------------------------------------------------------------------ + + +class ClangTidy(Command): + """A custom command to run Clang-Tidy on all C/C++ source files""" + + description = 'run Clang-Tidy on all C/C++ source files' + user_options = [('warning-as-errors', None, 'Warning as errors')] + boolean_options = ['warning-as-errors'] + + sub_commands = [('build_ext', None)] + + def initialize_options(self): + self.warning_as_errors = None + + def finalize_options(self): + pass + + def run(self): + # Ideally we would use self.run_command(command) but we need to ensure + # that --dry-run --gen-compiledb are passed to build_ext regardless of + # other arguments + command = 'build_ext' + distutils.log.info("running %s --dry-run --gen-compiledb", command) + cmd_obj = self.get_finalized_command(command) + cmd_obj.dry_run = True + cmd_obj.gen_compiledb = True + try: + cmd_obj.run() + self.distribution.have_run[command] = 1 + assert self.distribution.ext_modules + except BuildFailed: + distutils.log.error('build_ext --dry-run --gen-compiledb command failed!') + raise RuntimeError('build_ext --dry-run --gen-compiledb command failed!') + + command = ['clang-tidy'] + if self.warning_as_errors: + command.append('--warnings-as-errors=*') + for ext in self.distribution.ext_modules: + command.extend(os.path.abspath(p) for p in ext.sources) + spawn(command, dry_run=self.dry_run) + + +# ------------------------------------------------------------------------------ + + +class GenerateRequirementFile(Command): + """A custom command to list the dependencies of the current""" + + description = 'List the dependencies of the current package' + user_options = [('include-extras', None, 'Include "extras_require" into the list')] + boolean_options = ['include-extras'] + + def initialize_options(self): + self.include_extras = None + + def finalize_options(self): + pass + + def run(self): + with open('requirements.txt', 'w') as fd: + try: + for pkg in self.distribution.install_requires: + fd.write('{}\n'.format(pkg)) + if self.include_extras: + for name, pkgs in self.distribution.extras_require.items(): + for pkg in pkgs: + fd.write('{}\n'.format(pkg)) + + except TypeError: # Mostly for old setuptools (< 30.x) + for pkg in self.distribution.command_options['options']['install_requires']: + fd.write('{}\n'.format(pkg)) + if self.include_extras: + for name, pkgs in self.distribution.command_options['options.extras_require'].items(): + location, pkgs = pkgs + for pkg in pkgs.split(): + fd.write('{}\n'.format(pkg)) + + +# ------------------------------------------------------------------------------ + class Distribution(_Distribution): def has_ext_modules(self): @@ -410,28 +576,17 @@ def run_setup(with_cext): else: kwargs['ext_modules'] = [] - setup(name='projectq', - version=__version__, - author='ProjectQ', - author_email='info@projectq.ch', - url='http://www.projectq.ch', - project_urls={ - 'Documentation': 'https://projectq.readthedocs.io/en/latest/', - 'Issue Tracker': - 'https://github.com/ProjectQ-Framework/ProjectQ/', - }, - description=( - 'ProjectQ - ' - 'An open source software framework for quantum computing'), - long_description=long_description, - install_requires=requirements, - cmdclass={'build_ext': BuildExt}, - zip_safe=False, - license='Apache 2', - packages=find_packages(), - distclass=Distribution, - extras_require={'braket': ['boto3', ]}, - **kwargs) + setup( + use_scm_version={'local_scheme': 'no-local-version'}, + setup_requires=['setuptools_scm'], + cmdclass={ + 'build_ext': BuildExt, + 'clang_tidy': ClangTidy, + 'gen_reqfile': GenerateRequirementFile, + }, + distclass=Distribution, + **kwargs, + ) # ============================================================================== @@ -439,15 +594,13 @@ def run_setup(with_cext): if not cpython: run_setup(False) important_msgs( - 'WARNING: C/C++ extensions are not supported on ' - + 'some features are disabled (e.g. C++ simulator).', + 'WARNING: C/C++ extensions are not supported on some features are disabled (e.g. C++ simulator).', 'Plain-Python build succeeded.', ) elif os.environ.get('DISABLE_PROJECTQ_CEXT'): run_setup(False) important_msgs( - 'DISABLE_PROJECTQ_CEXT is set; ' - + 'not attempting to build C/C++ extensions.', + 'DISABLE_PROJECTQ_CEXT is set; not attempting to build C/C++ extensions.', 'Plain-Python build succeeded.', ) From 0cf7322cde910f79c6d4515fed36beaad2ae2f40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jun 2021 11:34:49 +0200 Subject: [PATCH 07/11] Bump joerick/cibuildwheel from 1.11.0 to 1.11.1 (#397) * Bump joerick/cibuildwheel from 1.11.0 to 1.11.1 Bumps [joerick/cibuildwheel](https://github.com/joerick/cibuildwheel) from 1.11.0 to 1.11.1. - [Release notes](https://github.com/joerick/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/joerick/cibuildwheel/compare/v1.11.0...v1.11.1) --- updated-dependencies: - dependency-name: joerick/cibuildwheel dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update CHANGELOG Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Damien Nguyen --- .github/workflows/publish_release.yml | 2 +- CHANGELOG.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 3bb2ab43e..abc0a7364 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -28,7 +28,7 @@ jobs: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Build wheels - uses: joerick/cibuildwheel@v1.11.0 + uses: joerick/cibuildwheel@v1.11.1 env: CIBW_ARCHS: auto64 CIBW_SKIP: cp27-* pp* cp35-* diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e6d331f2..5a247e97e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Compatibility with Python 2.7 +### Repository + +- Updated cibuildwheels to 1.11.1 + ## [0.5.1] - 2019-02-15 ### Added From b078f67eebc25ea3aae229abcd2d97f3bc2c3380 Mon Sep 17 00:00:00 2001 From: Alex Milstead Date: Mon, 14 Jun 2021 11:32:42 -0400 Subject: [PATCH 08/11] Add a backend for IonQ. (#391) * Add a backend for IonQ. * Update projectq/setups/ionq.py Thanks for the correction! Co-authored-by: Nguyen Damien * Update docstring to match return type. * Fix copyright header years, imports, docstring. * Fix comment * Fix measurement mappings and result parsing. * Fix some bad logic with result probability mappings. * Fix some erroneous test cases. * Fix example code. * Ensure qubits are "deallocated" and reset after job submit. * Drop dependency on mappers in favor of API response mappings. * Add better error handling for API/http errors. * Fix tests. * Update example notebook. * Fix qubit mapping. * Use a dedicated qubit mapper instead of bashing self.main_engine. * Update backend, add tests, to reflect new mapper. * Use response.registers instead of echoing metadata. * Fix get_probability after register updates * Fixes after rebase, use metadata to remamp qubit IDs on response * Lint/CI/Format/Changelog fixes * Always make sure to give a result, even if it's not likely * Missed formatter issues Co-authored-by: Nguyen Damien --- CHANGELOG.md | 1 + README.rst | 33 +- examples/ionq.ipynb | 367 +++++++++++ examples/ionq.py | 85 +++ examples/ionq_bv.py | 90 +++ examples/ionq_half_adder.py | 89 +++ projectq/backends/__init__.py | 2 + projectq/backends/_ionq/__init__.py | 18 + projectq/backends/_ionq/_ionq.py | 388 ++++++++++++ projectq/backends/_ionq/_ionq_exc.py | 50 ++ projectq/backends/_ionq/_ionq_http_client.py | 401 ++++++++++++ .../backends/_ionq/_ionq_http_client_test.py | 570 ++++++++++++++++++ projectq/backends/_ionq/_ionq_mapper.py | 83 +++ projectq/backends/_ionq/_ionq_mapper_test.py | 128 ++++ projectq/backends/_ionq/_ionq_test.py | 519 ++++++++++++++++ projectq/setups/ionq.py | 69 +++ projectq/setups/ionq_test.py | 47 ++ 17 files changed, 2939 insertions(+), 1 deletion(-) create mode 100644 examples/ionq.ipynb create mode 100644 examples/ionq.py create mode 100644 examples/ionq_bv.py create mode 100644 examples/ionq_half_adder.py create mode 100644 projectq/backends/_ionq/__init__.py create mode 100644 projectq/backends/_ionq/_ionq.py create mode 100644 projectq/backends/_ionq/_ionq_exc.py create mode 100644 projectq/backends/_ionq/_ionq_http_client.py create mode 100644 projectq/backends/_ionq/_ionq_http_client_test.py create mode 100644 projectq/backends/_ionq/_ionq_mapper.py create mode 100644 projectq/backends/_ionq/_ionq_mapper_test.py create mode 100644 projectq/backends/_ionq/_ionq_test.py create mode 100644 projectq/setups/ionq.py create mode 100644 projectq/setups/ionq_test.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a247e97e..154ad88ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added ``.editorconfig` file - Added ``pyproject.toml`` and ``setup.cfg`` - Added CHANGELOG.md +- Added backend for IonQ. ### Deprecated diff --git a/README.rst b/README.rst index 5232cd889..c93c2165b 100755 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ targeting various types of hardware, a high-performance quantum computer simulator with emulation capabilities, and various compiler plug-ins. This allows users to -- run quantum programs on the IBM Quantum Experience chip, AQT devices or AWS Braket service provided devices +- run quantum programs on the IBM Quantum Experience chip, AQT devices, AWS Braket, or IonQ service provided devices - simulate quantum programs on classical computers - emulate quantum programs at a higher level of abstraction (e.g., mimicking the action of large oracles instead of compiling them to @@ -172,6 +172,37 @@ IonQ from IonQ and the state vector simulator SV1: python3 -m pip install -ve .[braket] +**Running a quantum program on IonQ devices** + +To run a program on the IonQ trapped ion hardware, use the `IonQBackend` and its corresponding setup. + +Currently available devices are: + +* `ionq_simulator`: A 29-qubit simulator. +* `ionq_qpu`: A 11-qubit trapped ion system. + +.. code-block:: python + + import projectq.setups.ionq + from projectq import MainEngine + from projectq.backends import IonQBackend + + token = 'MY_TOKEN' + device = 'ionq_qpu' + backend = IonQBackend( + token=token, + use_hardware=True, + num_runs=1024, + verbose=False, + device=device, + ) + compiler_engines = projectq.setups.ionq.get_engine_list( + token=token, + device=device, + ) + eng = MainEngine(backend, engine_list=compiler_engines) + + **Classically simulate a quantum program** ProjectQ has a high-performance simulator which allows simulating up to about 30 qubits on a regular laptop. See the `simulator tutorial `__ for more information. Using the emulation features of our simulator (fast classical shortcuts), one can easily emulate Shor's algorithm for problem sizes for which a quantum computer would require above 50 qubits, see our `example codes `__. diff --git a/examples/ionq.ipynb b/examples/ionq.ipynb new file mode 100644 index 000000000..a324c35a9 --- /dev/null +++ b/examples/ionq.ipynb @@ -0,0 +1,367 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# IonQ ProjectQ Backend Example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook will walk you through a basic example of using IonQ hardware to run ProjectQ circuits." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "The only requirement to run ProjectQ circuits on IonQ hardware is an IonQ API token.\n", + "\n", + "Once you have acquired a token, please try out the examples in this notebook!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Usage & Examples\n", + "\n", + "\n", + "**NOTE**: The `IonQBackend` expects an API key to be supplied via the `token` keyword argument to its constructor. If no token is directly provided, the backend will prompt you for one.\n", + "\n", + "The `IonQBackend` currently supports two device types:\n", + "* `ionq_simulator`: IonQ's simulator backend.\n", + "* `ionq_qpu`: IonQ's QPU backend.\n", + "\n", + "To view the latest list of available devices, you can run the `show_devices` function in the `projectq.backends._ionq._ionq_http_client` module." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# NOTE: Optional! This ignores warnings emitted from ProjectQ imports.\n", + "import warnings\n", + "warnings.filterwarnings('ignore')\n", + "\n", + "# Import ProjectQ and IonQBackend objects, the setup an engine\n", + "import projectq.setups.ionq\n", + "from projectq import MainEngine\n", + "from projectq.backends import IonQBackend\n", + "\n", + "# REPLACE WITH YOUR API TOKEN\n", + "token = 'your api token'\n", + "device = 'ionq_simulator'\n", + "\n", + "# Create an IonQBackend\n", + "backend = IonQBackend(\n", + " use_hardware=True,\n", + " token=token,\n", + " num_runs=200,\n", + " device=device,\n", + ")\n", + "\n", + "# Make sure to get an engine_list from the ionq setup module\n", + "engine_list = projectq.setups.ionq.get_engine_list(\n", + " token=token,\n", + " device=device,\n", + ")\n", + "\n", + "# Create a ProjectQ engine\n", + "engine = MainEngine(backend, engine_list)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example — Bell Pair" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Notes about running circuits on IonQ backends\n", + "Circuit building and visualization should feel identical to building a circuit using any other backend with ProjectQ. \n", + "\n", + "That said, there are a couple of things to note when running on IonQ backends: \n", + " \n", + "- IonQ backends do not allow arbitrary unitaries, mid-circuit resets or measurements, or multi-experiment jobs. In practice, this means using `reset`, `initialize`, `u` `u1`, `u2`, `u3`, `cu`, `cu1`, `cu2`, or `cu3` gates will throw an exception on submission, as will measuring mid-circuit, and submmitting jobs with multiple experiments.\n", + "- While `barrier` is allowed for organizational and visualization purposes, the IonQ compiler does not see it as a compiler directive." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's make a simple Bell pair circuit:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import gates to apply:\n", + "from projectq.ops import All, H, CNOT, Measure\n", + "\n", + "# Allocate two qubits\n", + "circuit = engine.allocate_qureg(2)\n", + "qubit0, qubit1 = circuit\n", + "\n", + "# add gates — here we're creating a simple bell pair\n", + "H | qubit0\n", + "CNOT | (qubit0, qubit1)\n", + "All(Measure) | circuit" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Run the bell pair circuit\n", + "Now, let's run our bell pair circuit on the simulator. \n", + "\n", + "All that is left is to call the main engine's `flush` method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Flush the circuit, which will submit the circuit to IonQ's API for processing\n", + "engine.flush()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# If all went well, we can view results from the circuit execution\n", + "probabilities = engine.backend.get_probabilities(circuit)\n", + "print(probabilities)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also use the built-in matplotlib support to plot the histogram of results:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# show a plot of result probabilities\n", + "import matplotlib.pyplot as plt\n", + "from projectq.libs.hist import histogram\n", + "\n", + "# Show the histogram\n", + "histogram(engine.backend, circuit)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example - Bernstein-Vazirani\n", + "\n", + "\n", + "For our second example, let's build a Bernstein-Vazirani circuit and run it on a real IonQ quantum computer.\n", + "\n", + "Rather than manually building the BV circuit every time, we'll create a method that can build one for any oracle $s$, and any register size." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from projectq.ops import All, H, Z, CX, Measure\n", + "\n", + "\n", + "def oracle(qureg, input_size, s_int):\n", + " \"\"\"Apply the 'oracle'.\"\"\"\n", + "\n", + " s = ('{0:0' + str(input_size) + 'b}').format(s_int)\n", + "\n", + " for bit in range(input_size):\n", + " if s[input_size - 1 - bit] == '1':\n", + " CX | (qureg[bit], qureg[input_size])\n", + "\n", + " \n", + "def run_bv_circuit(eng, s_int, input_size):\n", + " \"\"\"build the Bernstein-Vazirani circuit\n", + " \n", + " Args:\n", + " eng (MainEngine): A ProjectQ engine instance with an IonQBackend.\n", + " s_int (int): value of s, the secret bitstring, as an integer\n", + " input_size (int): size of the input register, \n", + " i.e. the number of (qu)bits to use for the binary \n", + " representation of s\n", + " \"\"\"\n", + " # confirm the bitstring of S is what we think it should be\n", + " s = ('{0:0' + str(input_size) + 'b}').format(s_int)\n", + " print('s: ', s)\n", + " \n", + " # We need a circuit with `input_size` qubits, plus one ancilla qubit\n", + " # Also need `input_size` classical bits to write the output to\n", + " circuit = eng.allocate_qureg(input_size + 1)\n", + " qubits = circuit[:-1]\n", + " output = circuit[input_size]\n", + "\n", + " # put ancilla in state |-⟩\n", + " H | output\n", + " Z | output\n", + " \n", + " # Apply Hadamard gates before querying the oracle\n", + " All(H) | qubits\n", + " \n", + " # Apply the inner-product oracle\n", + " oracle(circuit, input_size, s_int)\n", + "\n", + " # Apply Hadamard gates after querying the oracle\n", + " All(H) | qubits\n", + "\n", + " # Measurement\n", + " All(Measure) | qubits\n", + "\n", + " return qubits\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's use that method to create a BV circuit to submit:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "# Run a BV circuit:\n", + "s_int = 3\n", + "input_size = 3\n", + "\n", + "circuit = run_bv_circuit(engine, s_int, input_size)\n", + "engine.flush()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Time to run it on an IonQ QPU!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create an IonQBackend set to use the 'ionq_qpu' device\n", + "device = 'ionq_qpu'\n", + "backend = IonQBackend(\n", + " use_hardware=True,\n", + " token=token,\n", + " num_runs=100,\n", + " device=device,\n", + ")\n", + "\n", + "# Make sure to get an engine_list from the ionq setup module\n", + "engine_list = projectq.setups.ionq.get_engine_list(\n", + " token=token,\n", + " device=device,\n", + ")\n", + "\n", + "# Create a ProjectQ engine\n", + "engine = MainEngine(backend, engine_list)\n", + "\n", + "# Setup another BV circuit\n", + "circuit = run_bv_circuit(engine, s_int, input_size)\n", + "\n", + "# Run the circuit!\n", + "engine.flush()\n", + "\n", + "# Show the histogram\n", + "histogram(engine.backend, circuit)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Because QPU time is a limited resource, QPU jobs are handled in a queue and may take a while to complete. The IonQ backend accounts for this delay by providing basic attributes which may be used to tweak the behavior of the backend while it waits on job results: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create an IonQ backend with custom job fetch/wait settings\n", + "backend = IonQBackend(\n", + " token=token,\n", + " device=device,\n", + " num_runs=100,\n", + " use_hardware=True,\n", + " # Number of times to check for results before giving up\n", + " num_retries=3000,\n", + " # The number of seconds to wait between attempts\n", + " interval=1,\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "name": "python379jvsc74a57bd083bb9cfe1c33ba3c1386f3a99c53663f4ea55973353f0ef3c6be0ff58dd42d14", + "display_name": "Python 3.7.9 64-bit ('projectq': pyenv)" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/ionq.py b/examples/ionq.py new file mode 100644 index 000000000..1eaf2d136 --- /dev/null +++ b/examples/ionq.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. +"""Example of a basic entangling operation using an IonQBackend.""" + +import getpass + +import matplotlib.pyplot as plt + +import projectq.setups.ionq +from projectq import MainEngine +from projectq.backends import IonQBackend +from projectq.libs.hist import histogram +from projectq.ops import All, Entangle, Measure + + +def run_entangle(eng, num_qubits=3): + """ + Runs an entangling operation on the provided compiler engine. + + Args: + eng (MainEngine): Main compiler engine to use. + num_qubits (int): Number of qubits to entangle. + + Returns: + measurement (list): List of measurement outcomes. + """ + # allocate the quantum register to entangle + qureg = eng.allocate_qureg(num_qubits) + + # entangle the qureg + Entangle | qureg + + # measure; should be all-0 or all-1 + All(Measure) | qureg + + # run the circuit + eng.flush() + + # access the probabilities via the back-end: + # results = eng.backend.get_probabilities(qureg) + # for state in results: + # print("Measured {} with p = {}.".format(state, results[state])) + # or plot them directly: + histogram(eng.backend, qureg) + plt.show() + + # return one (random) measurement outcome. + return [int(q) for q in qureg] + + +if __name__ == '__main__': + token = None + device = None + if token is None: + token = getpass.getpass(prompt='IonQ apiKey > ') + if device is None: + device = input('IonQ device > ') + + # create an IonQBackend + backend = IonQBackend( + use_hardware=True, + token=token, + num_runs=200, + verbose=True, + device=device, + ) + engine_list = projectq.setups.ionq.get_engine_list( + token=token, + device=device, + ) + engine = MainEngine(backend, engine_list) + # run the circuit and print the result + print(run_entangle(engine)) diff --git a/examples/ionq_bv.py b/examples/ionq_bv.py new file mode 100644 index 000000000..a1135db8c --- /dev/null +++ b/examples/ionq_bv.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. +"""Example of a basic Bernstein-Vazirani circuit using an IonQBackend.""" + +import getpass +import random + +import matplotlib.pyplot as plt + +import projectq.setups.ionq +from projectq import MainEngine +from projectq.backends import IonQBackend +from projectq.libs.hist import histogram +from projectq.ops import CX, All, Barrier, H, Measure, Z + + +def oracle(qureg, input_size, s): + """Apply the 'oracle'.""" + + for bit in range(input_size): + if s[input_size - 1 - bit] == '1': + CX | (qureg[bit], qureg[input_size]) + + +def run_bv_circuit(eng, input_size, s_int): + s = ('{0:0' + str(input_size) + 'b}').format(s_int) + print("Secret string: ", s) + print("Number of qubits: ", str(input_size + 1)) + circuit = eng.allocate_qureg(input_size + 1) + All(H) | circuit + Z | circuit[input_size] + + Barrier | circuit + + oracle(circuit, input_size, s) + + Barrier | circuit + + qubits = circuit[:input_size] + All(H) | qubits + All(Measure) | qubits + eng.flush() + + # return a random answer from our results + histogram(eng.backend, qubits) + plt.show() + + # return a random answer from our results + probabilities = eng.backend.get_probabilities(qubits) + random_answer = random.choice(list(probabilities.keys())) + print("Probability of getting correct string: ", probabilities[s[::-1]]) + return [int(s) for s in random_answer] + + +if __name__ == '__main__': + token = None + device = None + if token is None: + token = getpass.getpass(prompt='IonQ apiKey > ') + if device is None: + device = input('IonQ device > ') + + # create main compiler engine for the IonQ back-end + backend = IonQBackend( + use_hardware=True, + token=token, + num_runs=1, + verbose=False, + device=device, + ) + engine_list = projectq.setups.ionq.get_engine_list( + token=token, + device=device, + ) + engine = MainEngine(backend, engine_list) + + # run the circuit and print the result + print(run_bv_circuit(engine, 3, 3)) diff --git a/examples/ionq_half_adder.py b/examples/ionq_half_adder.py new file mode 100644 index 000000000..33fb995e0 --- /dev/null +++ b/examples/ionq_half_adder.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. +"""Example of a basic 'half-adder' circuit using an IonQBackend""" + +import getpass +import random + +import matplotlib.pyplot as plt + +import projectq.setups.default +import projectq.setups.ionq +from projectq import MainEngine +from projectq.backends import IonQBackend +from projectq.libs.hist import histogram +from projectq.ops import CNOT, All, Barrier, Measure, Toffoli, X + + +def run_half_adder(eng): + # allocate the quantum register to entangle + circuit = eng.allocate_qureg(4) + qubit1, qubit2, qubit3, qubit4 = circuit + result_qubits = [qubit3, qubit4] + + # X gates on the first two qubits + All(X) | [qubit1, qubit2] + + # Barrier + Barrier | circuit + + # Cx gates + CNOT | (qubit1, qubit3) + CNOT | (qubit2, qubit3) + + # CCNOT + Toffoli | (qubit1, qubit2, qubit4) + + # Barrier + Barrier | circuit + + # Measure result qubits + All(Measure) | result_qubits + + # Flush the circuit (this submits a job to the IonQ API) + eng.flush() + + # Show the histogram + histogram(eng.backend, result_qubits) + plt.show() + + # return a random answer from our results + probabilities = eng.backend.get_probabilities(result_qubits) + random_answer = random.choice(list(probabilities.keys())) + return [int(s) for s in random_answer] + + +if __name__ == '__main__': + token = None + device = None + if token is None: + token = getpass.getpass(prompt='IonQ apiKey > ') + if device is None: + device = input('IonQ device > ') + + backend = IonQBackend( + use_hardware=True, + token=token, + num_runs=200, + verbose=True, + device=device, + ) + engine_list = projectq.setups.ionq.get_engine_list( + token=token, + device=device, + ) + engine = MainEngine(backend, engine_list) + # run the circuit and print the result + print(run_half_adder(engine)) diff --git a/projectq/backends/__init__.py b/projectq/backends/__init__.py index ae6a2c3f4..2b6ce5520 100755 --- a/projectq/backends/__init__.py +++ b/projectq/backends/__init__.py @@ -26,6 +26,7 @@ * an interface to the IBM Quantum Experience chip (and simulator). * an interface to the AQT trapped ion system (and simulator). * an interface to the AWS Braket service decives (and simulators) +* an interface to the IonQ trapped ionq hardware (and simulator). """ from ._printer import CommandPrinter from ._circuits import CircuitDrawer, CircuitDrawerMatplotlib @@ -34,3 +35,4 @@ from ._ibm import IBMBackend from ._aqt import AQTBackend from ._awsbraket import AWSBraketBackend +from ._ionq import IonQBackend diff --git a/projectq/backends/_ionq/__init__.py b/projectq/backends/_ionq/__init__.py new file mode 100644 index 000000000..5269e8f23 --- /dev/null +++ b/projectq/backends/_ionq/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. + +from ._ionq import IonQBackend + +__all__ = ['IonQBackend'] diff --git a/projectq/backends/_ionq/_ionq.py b/projectq/backends/_ionq/_ionq.py new file mode 100644 index 000000000..055eaab61 --- /dev/null +++ b/projectq/backends/_ionq/_ionq.py @@ -0,0 +1,388 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. + +""" Back-end to run quantum programs using IonQ hardware.""" +import random + +from projectq.cengines import BasicEngine +from projectq.meta import LogicalQubitIDTag, get_control_count +from projectq.ops import ( + Allocate, + Barrier, + DaggeredGate, + Deallocate, + FlushGate, + HGate, + Measure, + R, + Rx, + Rxx, + Ry, + Ryy, + Rz, + Rzz, + Sdag, + SGate, + SqrtXGate, + SwapGate, + Tdag, + TGate, + XGate, + YGate, + ZGate, +) +from projectq.types import WeakQubitRef + +from . import _ionq_http_client as http_client +from ._ionq_exc import InvalidCommandError, MidCircuitMeasurementError + +GATE_MAP = { + XGate: 'x', + YGate: 'y', + ZGate: 'z', + HGate: 'h', + Rx: 'rx', + Ry: 'ry', + Rz: 'rz', + SGate: 's', + TGate: 't', + SqrtXGate: 'v', + Rxx: 'xx', + Ryy: 'yy', + Rzz: 'zz', + SwapGate: 'swap', +} +SUPPORTED_GATES = tuple(GATE_MAP.keys()) + + +def _rearrange_result(input_result, length): + """Turn ``input_result`` from an integer into a bit-string. + + Args: + input_result (int): An integer representation of qubit states. + length (int): The total number of bits (for padding, if needed). + + Returns: + str: A bit-string representation of ``input_result``. + """ + bin_input = list(bin(input_result)[2:].rjust(length, '0')) + return ''.join(bin_input)[::-1] + + +class IonQBackend(BasicEngine): + """Backend for building circuits and submitting them to the IonQ API.""" + + def __init__( + self, + use_hardware=False, + num_runs=100, + verbose=False, + token=None, + device='ionq_simulator', + num_retries=3000, + interval=1, + retrieve_execution=None, + ): + """Constructor for the IonQBackend. + + Args: + use_hardware (bool, optional): Whether or not to use real IonQ + hardware or just a simulator. If False, the ionq_simulator is + used regardless of the value of ``device``. Defaults to False. + num_runs (int, optional): Number of times to run circuits. Defaults to 100. + verbose (bool, optional): If True, print statistics after job + results have been collected. Defaults to False. + token (str, optional): An IonQ API token. Defaults to None. + device (str, optional): Device to run jobs on. + Supported devices are ``'ionq_qpu'`` or ``'ionq_simulator'``. + Defaults to ``'ionq_simulator'``. + num_retries (int, optional): Number of times to retry fetching a + job after it has been submitted. Defaults to 3000. + interval (int, optional): Number of seconds to wait inbetween + result fetch retries. Defaults to 1. + retrieve_execution (str, optional): An IonQ API Job ID. + If provided, a job with this ID will be fetched. Defaults to None. + """ + BasicEngine.__init__(self) + self.device = device if use_hardware else 'ionq_simulator' + self._num_runs = num_runs + self._verbose = verbose + self._token = token + self._num_retries = num_retries + self._interval = interval + self._circuit = [] + self._measured_ids = [] + self._probabilities = dict() + self._retrieve_execution = retrieve_execution + self._clear = True + + def is_available(self, cmd): + """Test if this backend is available to process the provided command. + + Args: + cmd (Command): A command to process. + + Returns: + bool: If this backend can process the command. + """ + gate = cmd.gate + + # Metagates. + if gate in (Measure, Allocate, Deallocate, Barrier): + return True + + # CNOT gates. + # NOTE: IonQ supports up to 7 control qubits + num_ctrl_qubits = get_control_count(cmd) + if 0 < num_ctrl_qubits <= 7: + return isinstance(gate, (XGate,)) + + # Gates witout control bits. + if num_ctrl_qubits == 0: + supported = isinstance(gate, SUPPORTED_GATES) + supported_transpose = gate in (Sdag, Tdag) + return supported or supported_transpose + return False + + def _reset(self): + """Reset this backend. + + .. NOTE:: + + This sets ``_clear = True``, which will trigger state cleanup + on the next call to ``_store``. + """ + + # Lastly, reset internal state for measured IDs and circuit body. + self._circuit = [] + self._clear = True + + def _store(self, cmd): + """Interpret the ProjectQ command as a circuit instruction and store it. + + Args: + cmd (Command): A command to process. + + Raises: + InvalidCommandError: If the command can not be interpreted. + MidCircuitMeasurementError: If this command would result in a + mid-circuit qubit measurement. + """ + if self._clear: + self._measured_ids = [] + self._probabilities = dict() + self._clear = False + + # No-op/Meta gates. + # NOTE: self.main_engine.mapper takes care qubit allocation/mapping. + gate = cmd.gate + if gate in (Allocate, Deallocate, Barrier): + return + + # Create a measurement. + if gate == Measure: + assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 + logical_id = cmd.qubits[0][0].id + for tag in cmd.tags: + if isinstance(tag, LogicalQubitIDTag): + logical_id = tag.logical_qubit_id + break + # Add the qubit id + self._measured_ids.append(logical_id) + return + + # Process the Command's gate type: + gate_type = type(gate) + gate_name = GATE_MAP.get(gate_type) + # Daggered gates get special treatment. + if isinstance(gate, DaggeredGate): + gate_name = GATE_MAP[type(gate._gate)] + 'i' + + # Unable to determine a gate mapping here, so raise out. + if gate_name is None: + raise InvalidCommandError('Invalid command: ' + str(cmd)) + + # Now make sure there are no existing measurements on qubits involved + # in this operation. + targets = [qb.id for qureg in cmd.qubits for qb in qureg] + controls = [qb.id for qb in cmd.control_qubits] + if len(self._measured_ids) > 0: + + # Check any qubits we are trying to operate on. + gate_qubits = set(targets) | set(controls) + + # If any of them have already been measured... + already_measured = gate_qubits & set(self._measured_ids) + + # Boom! + if len(already_measured) > 0: + err = ( + 'Mid-circuit measurement is not supported. ' + 'The following qubits have already been measured: {}.'.format(list(already_measured)) + ) + raise MidCircuitMeasurementError(err) + + # Initialize the gate dict: + gate_dict = { + 'gate': gate_name, + 'targets': targets, + } + + # Check if we have a rotation + if isinstance(gate, (R, Rx, Ry, Rz, Rxx, Ryy, Rzz)): + gate_dict['rotation'] = gate.angle + + # Set controls + if len(controls) > 0: + gate_dict['controls'] = controls + + self._circuit.append(gate_dict) + + def get_probability(self, state, qureg): + """Shortcut to get a specific state's probability. + + Args: + state (str): A state in bit-string format. + qureg (Qureg): A ProjectQ Qureg object. + + Returns: + float: The probability for the provided state. + """ + if len(state) != len(qureg): + raise ValueError('Desired state and register must be the same length!') + + probs = self.get_probabilities(qureg) + return probs[state] + + def get_probabilities(self, qureg): + """Given the provided qubit register, determine the probability of + each possible outcome. + + .. NOTE:: + + This method should only be called *after* a circuit has been + run and its results are available. + + Args: + qureg (Qureg): A ProjectQ Qureg object. + + Returns: + dict: A dict mapping of states -> probability. + """ + if len(self._probabilities) == 0: + raise RuntimeError("Please, run the circuit first!") + + probability_dict = {} + for state in self._probabilities: + mapped_state = ['0'] * len(qureg) + for i, qubit in enumerate(qureg): + try: + meas_idx = self._measured_ids.index(qubit.id) + except ValueError: + continue + mapped_state[i] = state[meas_idx] + probability = self._probabilities[state] + mapped_state = "".join(mapped_state) + probability_dict[mapped_state] = probability_dict.get(mapped_state, 0) + probability + return probability_dict + + def _run(self): + """Run the circuit this object has built during engine execution.""" + # Nothing to do with an empty circuit. + if len(self._circuit) == 0: + return + + if self._retrieve_execution is None: + qubit_mapping = self.main_engine.mapper.current_mapping + measured_ids = self._measured_ids[:] + info = { + 'circuit': self._circuit, + 'nq': len(qubit_mapping.keys()), + 'shots': self._num_runs, + 'meas_mapped': [qubit_mapping[qubit_id] for qubit_id in measured_ids], + 'meas_qubit_ids': measured_ids, + } + res = http_client.send( + info, + device=self.device, + token=self._token, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) + if res is None: + raise RuntimeError('Failed to submit job to the server!') + else: + res = http_client.retrieve( + device=self.device, + token=self._token, + jobid=self._retrieve_execution, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose, + ) + if res is None: + raise RuntimeError("Failed to retrieve job with id: '{}'!".format(self._retrieve_execution)) + self._measured_ids = measured_ids = res['meas_qubit_ids'] + + # Determine random outcome from probable states. + P = random.random() + p_sum = 0.0 + measured = "" + star = "" + num_measured = len(measured_ids) + probable_outcomes = res['output_probs'] + states = probable_outcomes.keys() + self._probabilities = {} + for idx, state_int in enumerate(states): + state = _rearrange_result(int(state_int), num_measured) + probability = probable_outcomes[state_int] + p_sum += probability + if p_sum >= P and measured == "" or (idx == len(states) - 1): + measured = state + star = "*" + self._probabilities[state] = probability + if self._verbose and probability > 0: # pragma: no cover + print(state + " with p = " + str(probability) + star) + + # Register measurement results + for idx, qubit_id in enumerate(measured_ids): + result = int(measured[idx]) + qubit_ref = WeakQubitRef(self.main_engine, qubit_id) + self.main_engine.set_measurement_result(qubit_ref, result) + + def receive(self, command_list): + """Receive a command list from the ProjectQ engine pipeline. + + If a given command is a "flush" operation, the pending circuit will be + submitted to IonQ's API for processing. + + Args: + command_list (list[Command]): A list of ProjectQ Command objects. + """ + for cmd in command_list: + if not isinstance(cmd.gate, FlushGate): + self._store(cmd) + else: + # After that, the circuit is ready to be submitted. + try: + self._run() + finally: + # Make sure we always reset engine state so as not to leave + # anything dirty atexit. + self._reset() + + +__all__ = ['IonQBackend'] diff --git a/projectq/backends/_ionq/_ionq_exc.py b/projectq/backends/_ionq/_ionq_exc.py new file mode 100644 index 000000000..ad7b52e9e --- /dev/null +++ b/projectq/backends/_ionq/_ionq_exc.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. + +"""Error classes used by the IonQBackend and IonQ http client.""" + + +class DeviceTooSmall(Exception): + """Raised when a device does not have enough qubits for a desired job.""" + + +class DeviceOfflineError(Exception): + """Raised when a device is required but is currently offline.""" + + +class RequestTimeoutError(Exception): + """Raised if a request to IonQ's Job creation API times out.""" + + +class JobSubmissionError(Exception): + """Raised when the IonQ Job creation API contains an error of some kind.""" + + +class InvalidCommandError(Exception): + """Raised if the IonQBackend engine encounters an invalid command.""" + + +class MidCircuitMeasurementError(Exception): + """Raised when a mid-circuit measurement is detected on a qubit.""" + + +__all__ = [ + 'JobSubmissionError', + 'DeviceOfflineError', + 'DeviceTooSmall', + 'RequestTimeoutError', + 'InvalidCommandError', + 'MidCircuitMeasurementError', +] diff --git a/projectq/backends/_ionq/_ionq_http_client.py b/projectq/backends/_ionq/_ionq_http_client.py new file mode 100644 index 000000000..8bcab285f --- /dev/null +++ b/projectq/backends/_ionq/_ionq_http_client.py @@ -0,0 +1,401 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. + +""" HTTP Client for the IonQ API. """ + +import getpass +import json +import signal +import time + +import requests +from requests import Session +from requests.compat import urljoin + +from ._ionq_exc import ( + DeviceOfflineError, + DeviceTooSmall, + JobSubmissionError, + RequestTimeoutError, +) + +_API_URL = 'https://api.ionq.co/v0.1/jobs/' + + +class IonQ(Session): + """A requests.Session based HTTP client for the IonQ API.""" + + def __init__(self, verbose=False): + super(IonQ, self).__init__() + self.backends = dict() + self.timeout = 5.0 + self.token = None + self._verbose = verbose + + def update_devices_list(self): + """Update the list of devices this backend can support.""" + self.backends = { + 'ionq_simulator': { + 'nq': 29, + 'target': 'simulator', + }, + 'ionq_qpu': { + 'nq': 11, + 'target': 'qpu', + }, + } + if self._verbose: # pragma: no cover + print('- List of IonQ devices available:') + print(self.backends) + + def is_online(self, device): + """Check if a given device is online. + + Args: + device (str): An IonQ device name. + + Returns: + bool: True if device is online, else False. + """ + return device in self.backends + + def can_run_experiment(self, info, device): + """ + Determine whether or not the desired device has enough allocatable + qubits to run something. + + This returns a three-element tuple with whether or not the experiment + can be run, the max number of qubits possible, and the number of qubits + needed to run this experiment. + + Args: + info (dict): A dict containing number of shots, qubits, and + a circuit. + device (str): An IonQ device name. + Returns: + tuple(bool, int, int): Whether the operation can be run, max + number of qubits the device supports, and number of qubits + required for the experiment. + """ + 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): + """Set an Authorization header for this session. + + If no token is provided, an prompt will appear to ask for one. + + Args: + token (str): IonQ user API token. + """ + if token is None: + token = getpass.getpass(prompt='IonQ apiKey > ') + if not token: + raise RuntimeError('An authentication token is required!') + self.headers.update({'Authorization': 'apiKey {}'.format(token)}) + self.token = token + + def _run(self, info, device): + """Run a circuit from ``info`` on the specified ``device``. + + Args: + info (dict): A dict containing number of shots, qubits, and + a circuit. + device (str): An IonQ device name. + + Raises: + JobSubmissionError: If the job creation response from IonQ's API + had a failure result. + + Returns: + str: The ID of a newly submitted Job. + """ + argument = { + 'target': self.backends[device]['target'], + 'metadata': { + 'sdk': 'ProjectQ', + 'meas_qubit_ids': json.dumps(info['meas_qubit_ids']), + }, + 'shots': info['shots'], + 'registers': {'meas_mapped': info['meas_mapped']}, + 'lang': 'json', + 'body': { + 'qubits': info['nq'], + 'circuit': info['circuit'], + }, + } + + # _API_URL[:-1] strips the trailing slash. + # TODO: Add comprehensive error parsing for non-200 responses. + req = super(IonQ, self).post(_API_URL[:-1], json=argument) + req.raise_for_status() + + # Process the response. + r_json = req.json() + status = r_json['status'] + + # Return the job id. + if status == 'ready': + return r_json['id'] + + # Otherwise, extract any provided failure info and raise an exception. + failure = r_json.get('failure') or { + 'code': 'UnknownError', + 'error': 'An unknown error occurred!', + } + raise JobSubmissionError( + "{}: {} (status={})".format( + failure['code'], + failure['error'], + status, + ) + ) + + def _get_result(self, device, execution_id, num_retries=3000, interval=1): + """Given a backend and ID, fetch the results for this job's execution. + + The return dictionary should have at least: + + * ``nq`` (int): Number of qubits for this job. + * ``output_probs`` (dict): Map of integer states to probability values. + + Args: + device (str): The device used to run this job. + execution_id (str): An IonQ Job ID. + num_retries (int, optional): Number of times to retry the fetch + before raising a timeout error. Defaults to 3000. + interval (int, optional): Number of seconds to wait between retries. + Defaults to 1. + + Raises: + Exception: If the process receives a kill signal before completion. + Exception: If the job is in an unknown processing state. + DeviceOfflineError: If the provided device is not online. + RequestTimeoutError: If we were unable to retrieve the job results + after ``num_retries`` attempts. + + Returns: + dict: A dict of job data for an engine to consume. + """ + + if self._verbose: # pragma: no cover + print("Waiting for results. [Job ID: {}]".format(execution_id)) + + original_sigint_handler = signal.getsignal(signal.SIGINT) + + def _handle_sigint_during_get_result(*_): # pragma: no cover + raise Exception("Interrupted. The ID of your submitted job is {}.".format(execution_id)) + + signal.signal(signal.SIGINT, _handle_sigint_during_get_result) + + try: + for retries in range(num_retries): + req = super(IonQ, self).get(urljoin(_API_URL, execution_id)) + req.raise_for_status() + r_json = req.json() + status = r_json['status'] + + # Check if job is completed. + if status == 'completed': + meas_mapped = r_json['registers']['meas_mapped'] + meas_qubit_ids = json.loads(r_json['metadata']['meas_qubit_ids']) + output_probs = r_json['data']['registers']['meas_mapped'] + return { + 'nq': r_json['qubits'], + 'output_probs': output_probs, + 'meas_mapped': meas_mapped, + 'meas_qubit_ids': meas_qubit_ids, + } + + # Otherwise, make sure it is in a known healthy state. + if status not in ('ready', 'running', 'submitted'): + # TODO: Add comprehensive API error processing here. + raise Exception("Error while running the code: {}.".format(status)) + + # Sleep, then check availability before trying again. + time.sleep(interval) + if self.is_online(device) and retries % 60 == 0: + self.update_devices_list() + if not self.is_online(device): # pragma: no cover + 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 RequestTimeoutError("Timeout. The ID of your submitted job is {}.".format(execution_id)) + + +def show_devices(verbose=False): + """Show the currently available device list for the IonQ provider. + + Args: + verbose (bool): If True, additional information is printed + + Returns: + list: list of available devices and their properties. + """ + ionq_session = IonQ(verbose=verbose) + ionq_session.update_devices_list() + return ionq_session.backends + + +def retrieve( + device, + token, + jobid, + num_retries=3000, + interval=1, + verbose=False, +): + """Retrieve an already submitted IonQ job. + + Args: + device (str): The name of an IonQ device. + token (str): An IonQ API token. + jobid (str): An IonQ Job ID. + num_retries (int, optional): Number of times to retry while the job is + not finished. Defaults to 3000. + interval (int, optional): Sleep interval between retries, in seconds. + Defaults to 1. + verbose (bool, optional): Whether to print verbose output. + Defaults to False. + + Returns: + dict: A dict with job submission results. + """ + ionq_session = IonQ(verbose=verbose) + ionq_session._authenticate(token) + ionq_session.update_devices_list() + res = ionq_session._get_result( + device, + jobid, + num_retries=num_retries, + interval=interval, + ) + return res + + +def send( + info, + device='ionq_simulator', + token=None, + num_retries=100, + interval=1, + verbose=False, +): + """Submit a job to the IonQ API. + + The ``info`` dict should have at least the following keys:: + + * nq (int): Number of qubits this job will need. + * shots (dict): The number of shots to use for this job. + * meas_mapped (list): A list of qubits to measure. + * circuit (list): A list of JSON-serializable IonQ gate representations. + + Args: + info (dict): A dictionary with + device (str, optional): The IonQ device to run this on. Defaults to 'ionq_simulator'. + token (str, optional): An IonQ API token. Defaults to None. + num_retries (int, optional): Number of times to retry while the job is + not finished. Defaults to 100. + interval (int, optional): Sleep interval between retries, in seconds. + Defaults to 1. + verbose (bool, optional): Whether to print verbose output. + Defaults to False. + + Raises: + DeviceOfflineError: If the desired device is not available for job + processing. + DeviceTooSmall: If the job has a higher qubit requirement than the + device supports. + + Returns: + dict: An intermediate dict representation of an IonQ job result. + """ + try: + ionq_session = IonQ(verbose=verbose) + + if verbose: # pragma: no cover + print("- Authenticating...") + if verbose and token is not None: # pragma: no cover + print('user API token: ' + token) + ionq_session._authenticate(token) + + # check if the device is online + ionq_session.update_devices_list() + online = ionq_session.is_online(device) + + # useless for the moment + if not online: # pragma: no cover + 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 = ionq_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: # pragma: no cover + print("- Running code: {}".format(info)) + execution_id = ionq_session._run(info, device) + if verbose: # pragma: no cover + print("- Waiting for results...") + res = ionq_session._get_result( + device, + execution_id, + num_retries=num_retries, + interval=interval, + ) + if verbose: # pragma: no cover + print("- Done.") + return res + except requests.exceptions.HTTPError as err: + # Re-raise auth errors, as literally nothing else will work. + if err.response is not None: + status_code = err.response.status_code + if status_code in (401, 403): + raise err + + # Try to parse client errors + if status_code == 400: + err_json = err.response.json() + raise JobSubmissionError( + '{}: {}'.format( + err_json['error'], + err_json['message'], + ) + ) + + # Else, just print: + print("- There was an error running your code:") + print(err) + except requests.exceptions.RequestException as err: + print("- Looks like something is wrong with server:") + print(err) + + +__all__ = [ + 'send', + 'retrieve', + 'show_devices', + 'IonQ', +] diff --git a/projectq/backends/_ionq/_ionq_http_client_test.py b/projectq/backends/_ionq/_ionq_http_client_test.py new file mode 100644 index 000000000..7b09a00f8 --- /dev/null +++ b/projectq/backends/_ionq/_ionq_http_client_test.py @@ -0,0 +1,570 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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.backends._ionq._ionq_http_client.py.""" + +from unittest import mock + +import pytest +import requests +from requests.compat import urljoin + +from projectq.backends._ionq import _ionq_http_client +from projectq.backends._ionq._ionq_exc import JobSubmissionError, RequestTimeoutError + + +# Insure that no HTTP request can be made in all tests in this module +@pytest.fixture(autouse=True) +def no_requests(monkeypatch): + monkeypatch.delattr('requests.sessions.Session.request') + + +_api_url = 'https://api.ionq.co/v0.1/jobs/' + + +def test_authenticate(): + ionq_session = _ionq_http_client.IonQ() + ionq_session._authenticate('NotNone') + assert 'Authorization' in ionq_session.headers + assert ionq_session.token == 'NotNone' + assert ionq_session.headers['Authorization'] == 'apiKey NotNone' + + +def test_authenticate_prompt_requires_token(monkeypatch): + def user_password_input(prompt): + if prompt == 'IonQ apiKey > ': + return '' + + monkeypatch.setattr('getpass.getpass', user_password_input) + ionq_session = _ionq_http_client.IonQ() + with pytest.raises(RuntimeError) as excinfo: + ionq_session._authenticate() + assert str(excinfo.value) == 'An authentication token is required!' + + +def test_is_online(): + ionq_session = _ionq_http_client.IonQ() + ionq_session._authenticate('not none') + ionq_session.update_devices_list() + assert ionq_session.is_online('ionq_simulator') + assert ionq_session.is_online('ionq_qpu') + assert not ionq_session.is_online('ionq_unknown') + + +def test_show_devices(): + device_list = _ionq_http_client.show_devices() + assert isinstance(device_list, dict) + for info in device_list.values(): + assert 'nq' in info + assert 'target' in info + + +def test_send_too_many_qubits(monkeypatch): + # Patch the method to give back dummy devices + def _dummy_update(_self): + _self.backends = {'dummy': {'nq': 3, 'target': 'dummy'}} + + monkeypatch.setattr( + _ionq_http_client.IonQ, + 'update_devices_list', + _dummy_update.__get__(None, _ionq_http_client.IonQ), + ) + info = { + 'nq': 4, + 'shots': 1, + 'meas_mapped': [2, 3], + 'circuit': [ + {'gate': 'x', 'targets': [0]}, + {'gate': 'x', 'targets': [1]}, + {'controls': [0], 'gate': 'cnot', 'targets': [2]}, + {'controls': [1], 'gate': 'cnot', 'targets': [2]}, + {'controls': [0, 1], 'gate': 'cnot', 'targets': [3]}, + ], + } + with pytest.raises(_ionq_http_client.DeviceTooSmall): + _ionq_http_client.send( + info, + device='dummy', + token='NotNone', + verbose=True, + ) + + +def test_send_real_device_online_verbose(monkeypatch): + # Patch the method to give back dummy devices + def _dummy_update(_self): + _self.backends = {'dummy': {'nq': 4, 'target': 'dummy'}} + + monkeypatch.setattr( + _ionq_http_client.IonQ, + 'update_devices_list', + _dummy_update.__get__(None, _ionq_http_client.IonQ), + ) + # What the IonQ JSON API request should look like. + expected_request = { + 'target': 'dummy', + 'metadata': {'sdk': 'ProjectQ', 'meas_qubit_ids': '[2, 3]'}, + 'shots': 1, + 'registers': {'meas_mapped': [2, 3]}, + 'lang': 'json', + 'body': { + 'qubits': 4, + 'circuit': [ + {'gate': 'x', 'targets': [0]}, + {'gate': 'x', 'targets': [1]}, + {'controls': [0], 'gate': 'cnot', 'targets': [2]}, + {'controls': [1], 'gate': 'cnot', 'targets': [2]}, + {'controls': [0, 1], 'gate': 'cnot', 'targets': [3]}, + ], + }, + } + + def mock_post(_self, path, *args, **kwargs): + assert path == _api_url[:-1] + assert 'json' in kwargs + assert expected_request == kwargs['json'] + mock_response = mock.MagicMock() + mock_response.status_code = 200 + mock_response.json = mock.MagicMock( + return_value={ + 'id': 'new-job-id', + 'status': 'ready', + } + ) + return mock_response + + def mock_get(_self, path, *args, **kwargs): + assert urljoin(_api_url, 'new-job-id') == path + mock_response = mock.MagicMock() + mock_response.json = mock.MagicMock( + return_value={ + 'id': 'new-job-id', + 'status': 'completed', + 'qubits': 4, + 'metadata': {'meas_qubit_ids': '[2, 3]'}, + 'registers': {'meas_mapped': [2, 3]}, + 'data': { + 'registers': {'meas_mapped': {'2': 1}}, + }, + } + ) + return mock_response + + monkeypatch.setattr('requests.sessions.Session.post', mock_post) + monkeypatch.setattr('requests.sessions.Session.get', mock_get) + + def user_password_input(prompt): + if prompt == 'IonQ apiKey > ': + return 'NotNone' + + monkeypatch.setattr('getpass.getpass', user_password_input) + + # Code to test: + info = { + 'nq': 4, + 'shots': 1, + 'meas_mapped': [2, 3], + 'meas_qubit_ids': [2, 3], + 'circuit': [ + {'gate': 'x', 'targets': [0]}, + {'gate': 'x', 'targets': [1]}, + {'controls': [0], 'gate': 'cnot', 'targets': [2]}, + {'controls': [1], 'gate': 'cnot', 'targets': [2]}, + {'controls': [0, 1], 'gate': 'cnot', 'targets': [3]}, + ], + } + expected = { + 'nq': 4, + 'output_probs': {'2': 1}, + 'meas_mapped': [2, 3], + 'meas_qubit_ids': [2, 3], + } + actual = _ionq_http_client.send(info, device='dummy') + assert expected == actual + + +@pytest.mark.parametrize( + 'error_type', + [ + requests.exceptions.HTTPError, + requests.exceptions.RequestException, + ], +) +def test_send_requests_errors_are_caught(monkeypatch, error_type): + # Patch the method to give back dummy devices + def _dummy_update(_self): + _self.backends = {'dummy': {'nq': 4, 'target': 'dummy'}} + + monkeypatch.setattr( + _ionq_http_client.IonQ, + 'update_devices_list', + _dummy_update.__get__(None, _ionq_http_client.IonQ), + ) + mock_post = mock.MagicMock(side_effect=error_type()) + monkeypatch.setattr('requests.sessions.Session.post', mock_post) + + def user_password_input(prompt): + if prompt == 'IonQ apiKey > ': + return 'NotNone' + + monkeypatch.setattr('getpass.getpass', user_password_input) + + # Code to test: + info = { + 'nq': 1, + 'shots': 1, + 'meas_mapped': [], + 'meas_qubit_ids': [], + 'circuit': [], + } + _ionq_http_client.send(info, device='dummy') + mock_post.assert_called_once() + + +def test_send_auth_errors_reraise(monkeypatch): + # Patch the method to give back dummy devices + def _dummy_update(_self): + _self.backends = {'dummy': {'nq': 4, 'target': 'dummy'}} + + monkeypatch.setattr( + _ionq_http_client.IonQ, + 'update_devices_list', + _dummy_update.__get__(None, _ionq_http_client.IonQ), + ) + + mock_response = mock.MagicMock() + mock_response.status_code = 401 + auth_error = requests.exceptions.HTTPError(response=mock_response) + mock_post = mock.MagicMock(side_effect=auth_error) + monkeypatch.setattr('requests.sessions.Session.post', mock_post) + + def user_password_input(prompt): + if prompt == 'IonQ apiKey > ': + return 'NotNone' + + monkeypatch.setattr('getpass.getpass', user_password_input) + + # Code to test: + info = { + 'nq': 1, + 'shots': 1, + 'meas_mapped': [], + 'meas_qubit_ids': [], + 'circuit': [], + } + with pytest.raises(requests.exceptions.HTTPError) as excinfo: + _ionq_http_client.send(info, device='dummy') + mock_post.assert_called_once() + assert auth_error is excinfo.value + + +def test_send_bad_requests_reraise(monkeypatch): + # Patch the method to give back dummy devices + def _dummy_update(_self): + _self.backends = {'dummy': {'nq': 4, 'target': 'dummy'}} + + monkeypatch.setattr( + _ionq_http_client.IonQ, + 'update_devices_list', + _dummy_update.__get__(None, _ionq_http_client.IonQ), + ) + + mock_response = mock.MagicMock() + mock_response.status_code = 400 + mock_response.json = mock.MagicMock( + return_value={ + 'error': 'Bad Request', + 'message': 'Invalid request body', + } + ) + auth_error = requests.exceptions.HTTPError(response=mock_response) + mock_post = mock.MagicMock(side_effect=auth_error) + monkeypatch.setattr('requests.sessions.Session.post', mock_post) + + def user_password_input(prompt): + if prompt == 'IonQ apiKey > ': + return 'NotNone' + + monkeypatch.setattr('getpass.getpass', user_password_input) + + # Code to test: + info = { + 'nq': 1, + 'shots': 1, + 'meas_mapped': [], + 'meas_qubit_ids': [], + 'circuit': [], + } + with pytest.raises(JobSubmissionError) as excinfo: + _ionq_http_client.send(info, device='dummy') + mock_post.assert_called_once() + assert str(excinfo.value) == "Bad Request: Invalid request body" + + +def test_send_auth_token_required(monkeypatch): + # Patch the method to give back dummy devices + def _dummy_update(_self): + _self.backends = {'dummy': {'nq': 4, 'target': 'dummy'}} + + monkeypatch.setattr( + _ionq_http_client.IonQ, + 'update_devices_list', + _dummy_update.__get__(None, _ionq_http_client.IonQ), + ) + + mock_post = mock.MagicMock(side_effect=Exception()) + monkeypatch.setattr('requests.sessions.Session.post', mock_post) + + def user_password_input(prompt): + if prompt == 'IonQ apiKey > ': + return None + + monkeypatch.setattr('getpass.getpass', user_password_input) + + # Code to test: + info = { + 'nq': 1, + 'shots': 1, + 'meas_mapped': [], + 'meas_qubit_ids': [], + 'circuit': [], + } + with pytest.raises(RuntimeError) as excinfo: + _ionq_http_client.send(info, device='dummy') + mock_post.assert_not_called() + assert 'An authentication token is required!' == str(excinfo.value) + + +@pytest.mark.parametrize( + "expected_err, err_data", + [ + ( + "UnknownError: An unknown error occurred! (status=unknown)", + {'status': 'unknown'}, + ), + ( + 'APIError: Something failed! (status=failed)', + { + 'status': 'failed', + 'failure': { + 'error': 'Something failed!', + 'code': 'APIError', + }, + }, + ), + ], +) +def test_send_api_errors_are_raised(monkeypatch, expected_err, err_data): + # Patch the method to give back dummy devices + def _dummy_update(_self): + _self.backends = {'dummy': {'nq': 4, 'target': 'dummy'}} + + monkeypatch.setattr( + _ionq_http_client.IonQ, + 'update_devices_list', + _dummy_update.__get__(None, _ionq_http_client.IonQ), + ) + + def mock_post(_self, path, **kwargs): + assert _api_url[:-1] == path + mock_response = mock.MagicMock() + mock_response.json = mock.MagicMock(return_value=err_data) + return mock_response + + monkeypatch.setattr('requests.sessions.Session.post', mock_post) + + def user_password_input(prompt): + if prompt == 'IonQ apiKey > ': + return 'NotNone' + + monkeypatch.setattr('getpass.getpass', user_password_input) + + # Code to test: + info = { + 'nq': 1, + 'shots': 1, + 'meas_mapped': [], + 'meas_qubit_ids': [], + 'circuit': [], + } + with pytest.raises(JobSubmissionError) as excinfo: + _ionq_http_client.send(info, device='dummy') + + assert expected_err == str(excinfo.value) + + +def test_timeout_exception(monkeypatch): + # Patch the method to give back dummy devices + def _dummy_update(_self): + _self.backends = {'dummy': {'nq': 4, 'target': 'dummy'}} + + monkeypatch.setattr( + _ionq_http_client.IonQ, + 'update_devices_list', + _dummy_update.__get__(None, _ionq_http_client.IonQ), + ) + + def mock_post(_self, path, *args, **kwargs): + assert path == _api_url[:-1] + mock_response = mock.MagicMock() + mock_response.json = mock.MagicMock( + return_value={ + 'id': 'new-job-id', + 'status': 'ready', + } + ) + return mock_response + + def mock_get(_self, path, *args, **kwargs): + assert urljoin(_api_url, 'new-job-id') == path + mock_response = mock.MagicMock() + mock_response.json = mock.MagicMock( + return_value={ + 'id': 'new-job-id', + 'status': 'running', + } + ) + return mock_response + + monkeypatch.setattr('requests.sessions.Session.post', mock_post) + monkeypatch.setattr('requests.sessions.Session.get', mock_get) + + def user_password_input(prompt): + if prompt == 'IonQ apiKey > ': + return 'NotNone' + + monkeypatch.setattr('getpass.getpass', user_password_input) + + # Called once per loop in _get_result while the job is not ready. + mock_sleep = mock.MagicMock() + monkeypatch.setattr(_ionq_http_client.time, 'sleep', mock_sleep) + + # RequestTimeoutErrors are not caught, and so will raise out. + with pytest.raises(RequestTimeoutError) as excinfo: + info = { + 'nq': 1, + 'shots': 1, + 'meas_mapped': [], + 'meas_qubit_ids': [], + 'circuit': [], + } + _ionq_http_client.send(info, device='dummy', num_retries=1) + mock_sleep.assert_called_once() + assert 'Timeout. The ID of your submitted job is new-job-id.' == str(excinfo.value) + + +@pytest.mark.parametrize('token', [None, 'NotNone']) +def test_retrieve(monkeypatch, token): + def _dummy_update(_self): + _self.backends = {'dummy': {'nq': 4, 'target': 'dummy'}} + + monkeypatch.setattr( + _ionq_http_client.IonQ, + 'update_devices_list', + _dummy_update.__get__(None, _ionq_http_client.IonQ), + ) + request_num = [0] + + def mock_get(_self, path, *args, **kwargs): + assert urljoin(_api_url, 'old-job-id') == path + json_response = { + 'id': 'old-job-id', + 'status': 'running', + } + if request_num[0] > 1: + json_response = { + 'id': 'old-job-id', + 'status': 'completed', + 'qubits': 4, + 'registers': {'meas_mapped': [2, 3]}, + 'metadata': {'meas_qubit_ids': '[2, 3]'}, + 'data': { + 'registers': {'meas_mapped': {'2': 1}}, + }, + } + mock_response = mock.MagicMock() + mock_response.json = mock.MagicMock(return_value=json_response) + request_num[0] += 1 + return mock_response + + monkeypatch.setattr('requests.sessions.Session.get', mock_get) + + def user_password_input(prompt): + if prompt == 'IonQ apiKey > ': + return 'NotNone' + + monkeypatch.setattr('getpass.getpass', user_password_input) + + expected = { + 'nq': 4, + 'output_probs': {'2': 1}, + 'meas_qubit_ids': [2, 3], + 'meas_mapped': [2, 3], + } + + # Code to test: + # Called once per loop in _get_result while the job is not ready. + mock_sleep = mock.MagicMock() + monkeypatch.setattr(_ionq_http_client.time, 'sleep', mock_sleep) + result = _ionq_http_client.retrieve('dummy', token, 'old-job-id') + assert expected == result + # We only sleep twice. + assert 2 == mock_sleep.call_count + + +def test_retrieve_that_errors_are_caught(monkeypatch): + def _dummy_update(_self): + _self.backends = {'dummy': {'nq': 4, 'target': 'dummy'}} + + monkeypatch.setattr( + _ionq_http_client.IonQ, + 'update_devices_list', + _dummy_update.__get__(None, _ionq_http_client.IonQ), + ) + request_num = [0] + + def mock_get(_self, path, *args, **kwargs): + assert urljoin(_api_url, 'old-job-id') == path + json_response = { + 'id': 'old-job-id', + 'status': 'running', + } + if request_num[0] > 0: + json_response = { + 'id': 'old-job-id', + 'status': 'failed', + 'failure': { + 'code': 'ErrorCode', + 'error': 'A descriptive error message.', + }, + } + mock_response = mock.MagicMock() + mock_response.json = mock.MagicMock(return_value=json_response) + request_num[0] += 1 + return mock_response + + monkeypatch.setattr('requests.sessions.Session.get', mock_get) + + def user_password_input(prompt): + if prompt == 'IonQ apiKey > ': + return 'NotNone' + + monkeypatch.setattr('getpass.getpass', user_password_input) + + # Code to test: + mock_sleep = mock.MagicMock() + monkeypatch.setattr(_ionq_http_client.time, 'sleep', mock_sleep) + with pytest.raises(Exception): + _ionq_http_client.retrieve('dummy', 'NotNone', 'old-job-id') + mock_sleep.assert_called_once() diff --git a/projectq/backends/_ionq/_ionq_mapper.py b/projectq/backends/_ionq/_ionq_mapper.py new file mode 100644 index 000000000..9b7450b0d --- /dev/null +++ b/projectq/backends/_ionq/_ionq_mapper.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. +"""Mapper that has a max number of allocatable qubits.""" +from projectq.cengines import BasicMapperEngine +from projectq.meta import LogicalQubitIDTag +from projectq.ops import AllocateQubitGate, Command, DeallocateQubitGate, FlushGate +from projectq.types import WeakQubitRef + + +class BoundedQubitMapper(BasicMapperEngine): + def __init__(self, max_qubits): + super().__init__() + self._qubit_idx = 0 + self.max_qubits = max_qubits + + def _reset(self): + # Reset the mapping index. + self._qubit_idx = 0 + + def _process_cmd(self, cmd): + current_mapping = self.current_mapping + if current_mapping is None: + current_mapping = dict() + + if isinstance(cmd.gate, AllocateQubitGate): + qubit_id = cmd.qubits[0][0].id + if qubit_id in current_mapping: + raise RuntimeError("Qubit with id {} has already been allocated!".format(qubit_id)) + + if self._qubit_idx >= self.max_qubits: + raise RuntimeError("Cannot allocate more than {} qubits!".format(self.max_qubits)) + + new_id = self._qubit_idx + self._qubit_idx += 1 + current_mapping[qubit_id] = new_id + qb = WeakQubitRef(engine=self, idx=new_id) + new_cmd = Command( + engine=self, + gate=AllocateQubitGate(), + qubits=([qb],), + tags=[LogicalQubitIDTag(qubit_id)], + ) + self.current_mapping = current_mapping + self.send([new_cmd]) + elif isinstance(cmd.gate, DeallocateQubitGate): + qubit_id = cmd.qubits[0][0].id + if qubit_id not in current_mapping: + raise RuntimeError("Cannot deallocate a qubit that is not already allocated!") + qb = WeakQubitRef(engine=self, idx=current_mapping[qubit_id]) + new_cmd = Command( + engine=self, + gate=DeallocateQubitGate(), + qubits=([qb],), + tags=[LogicalQubitIDTag(qubit_id)], + ) + current_mapping.pop(qubit_id) + self.current_mapping = current_mapping + self.send([new_cmd]) + else: + self._send_cmd_with_mapped_ids(cmd) + + def receive(self, command_list): + for cmd in command_list: + if isinstance(cmd.gate, FlushGate): + self._reset() + self.send([cmd]) + else: + self._process_cmd(cmd) + + +__all__ = ['BoundedQubitMapper'] diff --git a/projectq/backends/_ionq/_ionq_mapper_test.py b/projectq/backends/_ionq/_ionq_mapper_test.py new file mode 100644 index 000000000..bf5784984 --- /dev/null +++ b/projectq/backends/_ionq/_ionq_mapper_test.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 ProjectQ-Framework (www.projectq.ch) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + +from projectq import MainEngine +from projectq.backends import Simulator +from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper +from projectq.meta import LogicalQubitIDTag +from projectq.ops import AllocateQubitGate, Command, DeallocateQubitGate +from projectq.types import WeakQubitRef + + +def test_cannot_allocate_past_max(): + engine = MainEngine( + Simulator(), + engine_list=[BoundedQubitMapper(1)], + verbose=True, + ) + engine.allocate_qubit() + with pytest.raises(RuntimeError) as excinfo: + engine.allocate_qubit() + + assert str(excinfo.value) == "Cannot allocate more than 1 qubits!" + + +def test_cannot_reallocate_same_qubit(): + engine = MainEngine( + Simulator(), + engine_list=[BoundedQubitMapper(1)], + verbose=True, + ) + qureg = engine.allocate_qubit() + qubit = qureg[0] + qubit_id = qubit.id + with pytest.raises(RuntimeError) as excinfo: + allocate_cmd = Command( + engine=engine, + gate=AllocateQubitGate(), + qubits=([WeakQubitRef(engine=engine, idx=qubit_id)],), + tags=[LogicalQubitIDTag(qubit_id)], + ) + engine.send([allocate_cmd]) + + assert str(excinfo.value) == "Qubit with id 0 has already been allocated!" + + +def test_cannot_deallocate_unknown_qubit(): + engine = MainEngine( + Simulator(), + engine_list=[BoundedQubitMapper(1)], + verbose=True, + ) + qureg = engine.allocate_qubit() + with pytest.raises(RuntimeError) as excinfo: + deallocate_cmd = Command( + engine=engine, + gate=DeallocateQubitGate(), + qubits=([WeakQubitRef(engine=engine, idx=1)],), + tags=[LogicalQubitIDTag(1)], + ) + engine.send([deallocate_cmd]) + assert str(excinfo.value) == "Cannot deallocate a qubit that is not already allocated!" + + # but we can still deallocate an already allocated one + qubit_id = qureg[0].id + deallocate_cmd = Command( + engine=engine, + gate=DeallocateQubitGate(), + qubits=([WeakQubitRef(engine=engine, idx=qubit_id)],), + tags=[LogicalQubitIDTag(qubit_id)], + ) + engine.send([deallocate_cmd]) + + +def test_cannot_deallocate_same_qubit(): + engine = MainEngine( + Simulator(), + engine_list=[BoundedQubitMapper(1)], + verbose=True, + ) + qureg = engine.allocate_qubit() + qubit = qureg[0] + qubit_id = qubit.id + engine.deallocate_qubit(qubit) + with pytest.raises(RuntimeError) as excinfo: + deallocate_cmd = Command( + engine=engine, + gate=DeallocateQubitGate(), + qubits=([WeakQubitRef(engine=engine, idx=qubit_id)],), + tags=[LogicalQubitIDTag(qubit_id)], + ) + engine.send([deallocate_cmd]) + + assert str(excinfo.value) == "Cannot deallocate a qubit that is not already allocated!" + + +def test_flush_deallocates_all_qubits(monkeypatch): + mapper = BoundedQubitMapper(10) + engine = MainEngine( + Simulator(), + engine_list=[mapper], + verbose=True, + ) + # needed to prevent GC from removing qubit refs + qureg = engine.allocate_qureg(10) + assert len(mapper.current_mapping.keys()) == 10 + assert len(engine.active_qubits) == 10 + engine.flush() + # Should still be around after flush + assert len(engine.active_qubits) == 10 + assert len(mapper.current_mapping.keys()) == 10 + + # GC will clean things up + del qureg + assert len(engine.active_qubits) == 0 + assert len(mapper.current_mapping.keys()) == 0 diff --git a/projectq/backends/_ionq/_ionq_test.py b/projectq/backends/_ionq/_ionq_test.py new file mode 100644 index 000000000..46458e603 --- /dev/null +++ b/projectq/backends/_ionq/_ionq_test.py @@ -0,0 +1,519 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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.backends._ionq._ionq.py.""" + +import math +from unittest import mock + +import pytest + +from projectq import MainEngine +from projectq.backends._ionq import _ionq, _ionq_http_client +from projectq.backends._ionq._ionq_exc import ( + InvalidCommandError, + MidCircuitMeasurementError, +) +from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper +from projectq.cengines import DummyEngine +from projectq.ops import ( + CNOT, + All, + Allocate, + Barrier, + Command, + Deallocate, + Entangle, + H, + Measure, + Ph, + R, + Rx, + Rxx, + Ry, + Rz, + S, + Sdag, + SqrtX, + T, + Tdag, + Toffoli, + X, + Y, + Z, +) +from projectq.types import Qubit, WeakQubitRef + + +@pytest.fixture(scope='function') +def mapper_factory(): + def _factory(n=4): + return BoundedQubitMapper(n) + + return _factory + + +# Prevent any requests from making it out. +@pytest.fixture(autouse=True) +def no_requests(monkeypatch): + monkeypatch.delattr("requests.sessions.Session.request") + + +@pytest.mark.parametrize( + "single_qubit_gate, is_available", + [ + (X, True), + (Y, True), + (Z, True), + (H, True), + (T, True), + (Tdag, True), + (S, True), + (Sdag, True), + (Allocate, True), + (Deallocate, True), + (SqrtX, True), + (Measure, True), + (Rx(0.5), True), + (Ry(0.5), True), + (Rz(0.5), True), + (R(0.5), False), + (Barrier, True), + (Entangle, False), + ], +) +def test_ionq_backend_is_available(single_qubit_gate, is_available): + eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) + qubit1 = eng.allocate_qubit() + ionq_backend = _ionq.IonQBackend() + cmd = Command(eng, single_qubit_gate, (qubit1,)) + assert ionq_backend.is_available(cmd) is is_available + + +# IonQ supports up to 7 control qubits. +@pytest.mark.parametrize( + "num_ctrl_qubits, is_available", + [ + (0, True), + (1, True), + (2, True), + (3, True), + (4, True), + (5, True), + (6, True), + (7, True), + (8, False), + ], +) +def test_ionq_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) + ionq_backend = _ionq.IonQBackend() + cmd = Command(eng, X, (qubit1,), controls=qureg) + assert ionq_backend.is_available(cmd) is is_available + + +def test_ionq_backend_init(): + """Test initialized backend has an empty circuit""" + backend = _ionq.IonQBackend(verbose=True, use_hardware=True) + assert hasattr(backend, '_circuit') + circuit = getattr(backend, '_circuit') + assert isinstance(circuit, list) + assert len(circuit) == 0 + + +def test_ionq_empty_circuit(): + """Test that empty circuits are still flushable.""" + backend = _ionq.IonQBackend(verbose=True) + eng = MainEngine(backend=backend) + eng.flush() + + +def test_ionq_no_circuit_executed(): + """Test that one can't retrieve probabilities if no circuit was run.""" + backend = _ionq.IonQBackend(verbose=True) + eng = MainEngine(backend=backend) + # no circuit has been executed -> raises exception + with pytest.raises(RuntimeError): + backend.get_probabilities([]) + eng.flush() + + +def test_ionq_get_probability(monkeypatch, mapper_factory): + """Test a shortcut for getting a specific state's probability""" + + def mock_retrieve(*args, **kwargs): + return { + 'nq': 3, + 'shots': 10, + 'output_probs': {'3': 0.4, '0': 0.6}, + 'meas_mapped': [0, 1], + 'meas_qubit_ids': [1, 2], + } + + monkeypatch.setattr(_ionq_http_client, "retrieve", mock_retrieve) + backend = _ionq.IonQBackend( + retrieve_execution="a3877d18-314f-46c9-86e7-316bc4dbe968", + verbose=True, + ) + eng = MainEngine(backend=backend, engine_list=[mapper_factory()]) + + unused_qubit = eng.allocate_qubit() # noqa: F841 + qureg = eng.allocate_qureg(2) + # entangle the qureg + Ry(math.pi / 2) | qureg[0] + Rx(math.pi / 2) | qureg[0] + Rx(math.pi / 2) | qureg[0] + Ry(math.pi / 2) | qureg[0] + Rxx(math.pi / 2) | (qureg[0], qureg[1]) + Rx(7 * math.pi / 2) | qureg[0] + Ry(7 * math.pi / 2) | qureg[0] + Rx(7 * math.pi / 2) | qureg[1] + + # measure; should be all-0 or all-1 + All(Measure) | qureg + # run the circuit + eng.flush() + assert eng.backend.get_probability('11', qureg) == pytest.approx(0.4) + assert eng.backend.get_probability('00', qureg) == pytest.approx(0.6) + + with pytest.raises(ValueError) as excinfo: + eng.backend.get_probability('111', qureg) + assert str(excinfo.value) == 'Desired state and register must be the same length!' + + +def test_ionq_get_probabilities(monkeypatch, mapper_factory): + """Test a shortcut for getting a specific state's probability""" + + def mock_retrieve(*args, **kwargs): + return { + 'nq': 3, + 'shots': 10, + 'output_probs': {'1': 0.4, '0': 0.6}, + 'meas_mapped': [1], + 'meas_qubit_ids': [1], + } + + monkeypatch.setattr(_ionq_http_client, "retrieve", mock_retrieve) + backend = _ionq.IonQBackend( + retrieve_execution="a3877d18-314f-46c9-86e7-316bc4dbe968", + verbose=True, + ) + eng = MainEngine(backend=backend, engine_list=[mapper_factory()]) + qureg = eng.allocate_qureg(2) + q0, q1 = qureg + H | q0 + CNOT | (q0, q1) + Measure | q1 + # run the circuit + eng.flush() + assert eng.backend.get_probability('01', qureg) == pytest.approx(0.4) + assert eng.backend.get_probability('00', qureg) == pytest.approx(0.6) + assert eng.backend.get_probability('1', [qureg[1]]) == pytest.approx(0.4) + assert eng.backend.get_probability('0', [qureg[1]]) == pytest.approx(0.6) + + +def test_ionq_invalid_command(): + """Test that this backend raises out with invalid commands.""" + + # Ph gate is not a valid gate + qb = WeakQubitRef(None, 1) + cmd = Command(None, gate=Ph(math.pi), qubits=[(qb,)]) + backend = _ionq.IonQBackend(verbose=True) + with pytest.raises(InvalidCommandError): + backend.receive([cmd]) + + +def test_ionq_sent_error(monkeypatch, mapper_factory): + """Test that errors on "send" will raise back out.""" + # patch send + type_error = TypeError() + mock_send = mock.MagicMock(side_effect=type_error) + monkeypatch.setattr(_ionq_http_client, "send", mock_send) + + backend = _ionq.IonQBackend() + eng = MainEngine( + backend=backend, + engine_list=[mapper_factory()], + verbose=True, + ) + qubit = eng.allocate_qubit() + Rx(0.5) | qubit + with pytest.raises(Exception) as excinfo: + qubit[0].__del__() + eng.flush() + + # verbose=True on the engine re-raises errors instead of compacting them. + assert type_error is excinfo.value + + # atexit sends another FlushGate, therefore we remove the backend: + dummy = DummyEngine() + dummy.is_last_engine = True + eng.next_engine = dummy + + +def test_ionq_send_nonetype_response_error(monkeypatch, mapper_factory): + """Test that no return value from "send" will raise a runtime error.""" + # patch send + mock_send = mock.MagicMock(return_value=None) + monkeypatch.setattr(_ionq_http_client, "send", mock_send) + + backend = _ionq.IonQBackend() + eng = MainEngine( + backend=backend, + engine_list=[mapper_factory()], + verbose=True, + ) + qubit = eng.allocate_qubit() + Rx(0.5) | qubit + with pytest.raises(RuntimeError) as excinfo: + eng.flush() + + # verbose=True on the engine re-raises errors instead of compacting them. + assert str(excinfo.value) == "Failed to submit job to the server!" + + # atexit sends another FlushGate, therefore we remove the backend: + dummy = DummyEngine() + dummy.is_last_engine = True + eng.next_engine = dummy + + +def test_ionq_retrieve(monkeypatch, mapper_factory): + """Test that initializing a backend with a jobid will fetch that job's results to use as its own""" + + def mock_retrieve(*args, **kwargs): + return { + 'nq': 3, + 'shots': 10, + 'output_probs': {'3': 0.4, '0': 0.6}, + 'meas_mapped': [0, 1], + 'meas_qubit_ids': [1, 2], + } + + monkeypatch.setattr(_ionq_http_client, "retrieve", mock_retrieve) + backend = _ionq.IonQBackend( + retrieve_execution="a3877d18-314f-46c9-86e7-316bc4dbe968", + verbose=True, + ) + eng = MainEngine(backend=backend, engine_list=[mapper_factory()]) + + unused_qubit = eng.allocate_qubit() + qureg = eng.allocate_qureg(2) + # entangle the qureg + Ry(math.pi / 2) | qureg[0] + Rx(math.pi / 2) | qureg[0] + Rx(math.pi / 2) | qureg[0] + Ry(math.pi / 2) | qureg[0] + Rxx(math.pi / 2) | (qureg[0], qureg[1]) + Rx(7 * math.pi / 2) | qureg[0] + Ry(7 * math.pi / 2) | qureg[0] + Rx(7 * math.pi / 2) | qureg[1] + del unused_qubit + # measure; should be all-0 or all-1 + All(Measure) | qureg + # run the circuit + eng.flush() + prob_dict = eng.backend.get_probabilities([qureg[0], qureg[1]]) + assert prob_dict['11'] == pytest.approx(0.4) + assert prob_dict['00'] == pytest.approx(0.6) + + # Unknown qubit + invalid_qubit = [Qubit(eng, 10)] + probs = eng.backend.get_probabilities(invalid_qubit) + assert {'0': 1} == probs + + +def test_ionq_retrieve_nonetype_response_error(monkeypatch, mapper_factory): + """Test that initializing a backend with a jobid will fetch that job's results to use as its own""" + + def mock_retrieve(*args, **kwargs): + return None + + monkeypatch.setattr(_ionq_http_client, "retrieve", mock_retrieve) + backend = _ionq.IonQBackend( + retrieve_execution="a3877d18-314f-46c9-86e7-316bc4dbe968", + verbose=True, + ) + eng = MainEngine( + backend=backend, + engine_list=[mapper_factory()], + verbose=True, + ) + + unused_qubit = eng.allocate_qubit() + qureg = eng.allocate_qureg(2) + # entangle the qureg + Ry(math.pi / 2) | qureg[0] + Rx(math.pi / 2) | qureg[0] + Rx(math.pi / 2) | qureg[0] + Ry(math.pi / 2) | qureg[0] + Rxx(math.pi / 2) | (qureg[0], qureg[1]) + Rx(7 * math.pi / 2) | qureg[0] + Ry(7 * math.pi / 2) | qureg[0] + Rx(7 * math.pi / 2) | qureg[1] + del unused_qubit + # measure; should be all-0 or all-1 + All(Measure) | qureg + # run the circuit + with pytest.raises(RuntimeError) as excinfo: + eng.flush() + + exc = excinfo.value + expected_err = "Failed to retrieve job with id: 'a3877d18-314f-46c9-86e7-316bc4dbe968'!" + assert str(exc) == expected_err + + +def test_ionq_backend_functional_test(monkeypatch, mapper_factory): + """Test that the backend can handle a valid circuit with valid results.""" + expected = { + 'nq': 3, + 'shots': 10, + 'meas_mapped': [1, 2], + 'meas_qubit_ids': [1, 2], + 'circuit': [ + {'gate': 'ry', 'rotation': 0.5, 'targets': [1]}, + {'gate': 'rx', 'rotation': 0.5, 'targets': [1]}, + {'gate': 'rx', 'rotation': 0.5, 'targets': [1]}, + {'gate': 'ry', 'rotation': 0.5, 'targets': [1]}, + {'gate': 'xx', 'rotation': 0.5, 'targets': [1, 2]}, + {'gate': 'rx', 'rotation': 3.5, 'targets': [1]}, + {'gate': 'ry', 'rotation': 3.5, 'targets': [1]}, + {'gate': 'rx', 'rotation': 3.5, 'targets': [2]}, + ], + } + + def mock_send(*args, **kwargs): + assert args[0] == expected + return { + 'nq': 3, + 'shots': 10, + 'output_probs': {'3': 0.4, '0': 0.6}, + 'meas_mapped': [1, 2], + 'meas_qubit_ids': [1, 2], + } + + monkeypatch.setattr(_ionq_http_client, "send", mock_send) + backend = _ionq.IonQBackend(verbose=True, num_runs=10) + eng = MainEngine( + backend=backend, + engine_list=[mapper_factory()], + verbose=True, + ) + unused_qubit = eng.allocate_qubit() # noqa: F841 + qureg = eng.allocate_qureg(2) + + # entangle the qureg + Ry(0.5) | qureg[0] + Rx(0.5) | qureg[0] + Rx(0.5) | qureg[0] + Ry(0.5) | qureg[0] + Rxx(0.5) | (qureg[0], qureg[1]) + Rx(3.5) | qureg[0] + Ry(3.5) | qureg[0] + Rx(3.5) | qureg[1] + All(Barrier) | qureg + # measure; should be all-0 or all-1 + All(Measure) | qureg + # run the circuit + eng.flush() + prob_dict = eng.backend.get_probabilities([qureg[0], qureg[1]]) + assert prob_dict['11'] == pytest.approx(0.4) + assert prob_dict['00'] == pytest.approx(0.6) + + +def test_ionq_backend_functional_aliases_test(monkeypatch, mapper_factory): + """Test that sub-classed or aliased gates are handled correctly.""" + # using alias gates, for coverage + expected = { + 'nq': 4, + 'shots': 10, + 'meas_mapped': [2, 3], + 'meas_qubit_ids': [2, 3], + 'circuit': [ + {'gate': 'x', 'targets': [0]}, + {'gate': 'x', 'targets': [1]}, + {'controls': [0], 'gate': 'x', 'targets': [2]}, + {'controls': [1], 'gate': 'x', 'targets': [2]}, + {'controls': [0, 1], 'gate': 'x', 'targets': [3]}, + {'gate': 's', 'targets': [2]}, + {'gate': 'si', 'targets': [3]}, + ], + } + + def mock_send(*args, **kwargs): + assert args[0] == expected + return { + 'nq': 4, + 'shots': 10, + 'output_probs': {'1': 0.9}, + 'meas_mapped': [2, 3], + } + + monkeypatch.setattr(_ionq_http_client, "send", mock_send) + backend = _ionq.IonQBackend(verbose=True, num_runs=10) + eng = MainEngine( + backend=backend, + engine_list=[mapper_factory(9)], + verbose=True, + ) + # Do some stuff with a circuit. Get weird with it. + circuit = eng.allocate_qureg(4) + qubit1, qubit2, qubit3, qubit4 = circuit + All(X) | [qubit1, qubit2] + CNOT | (qubit1, qubit3) + CNOT | (qubit2, qubit3) + Toffoli | (qubit1, qubit2, qubit4) + Barrier | circuit + S | qubit3 + Sdag | qubit4 + All(Measure) | [qubit3, qubit4] + + # run the circuit + eng.flush() + prob_dict = eng.backend.get_probabilities([qubit3, qubit4]) + assert prob_dict['10'] == pytest.approx(0.9) + + +def test_ionq_no_midcircuit_measurement(monkeypatch, mapper_factory): + """Test that attempts to measure mid-circuit raise exceptions.""" + + def mock_send(*args, **kwargs): + return { + 'nq': 1, + 'shots': 10, + 'output_probs': {'0': 0.4, '1': 0.6}, + } + + monkeypatch.setattr(_ionq_http_client, "send", mock_send) + + # Create a backend to use with an engine. + backend = _ionq.IonQBackend(verbose=True, num_runs=10) + eng = MainEngine( + backend=backend, + engine_list=[mapper_factory()], + verbose=True, + ) + qubit = eng.allocate_qubit() + X | qubit + Measure | qubit + with pytest.raises(MidCircuitMeasurementError): + X | qubit + + # atexit sends another FlushGate, therefore we remove the backend: + dummy = DummyEngine() + dummy.is_last_engine = True + eng.active_qubits = [] + eng.next_engine = dummy diff --git a/projectq/setups/ionq.py b/projectq/setups/ionq.py new file mode 100644 index 000000000..c68251eb9 --- /dev/null +++ b/projectq/setups/ionq.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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 allowing to compile code for IonQ trapped ion devices: +->The 11 qubit device +->The 29 qubits simulator +""" +from projectq.backends._ionq._ionq_exc import DeviceOfflineError +from projectq.backends._ionq._ionq_http_client import show_devices +from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper +from projectq.ops import ( + Barrier, + H, + Rx, + Rxx, + Ry, + Ryy, + Rz, + Rzz, + S, + Sdag, + SqrtX, + Swap, + T, + Tdag, + X, + Y, + Z, +) +from projectq.setups import restrictedgateset + + +def get_engine_list(token=None, device=None): + devices = show_devices(token) + if not device or device not in devices: + raise DeviceOfflineError("Error checking engine list: no '{}' devices available".format(device)) + + # + # Qubit mapper + # + mapper = BoundedQubitMapper(devices[device]['nq']) + + # + # Basis Gates + # + + # Declare the basis gateset for the IonQ's API. + engine_list = restrictedgateset.get_engine_list( + one_qubit_gates=(X, Y, Z, Rx, Ry, Rz, H, S, Sdag, T, Tdag, SqrtX), + two_qubit_gates=(Swap, Rxx, Ryy, Rzz), + other_gates=(Barrier,), + ) + return engine_list + [mapper] + + +__all__ = ['get_engine_list'] diff --git a/projectq/setups/ionq_test.py b/projectq/setups/ionq_test.py new file mode 100644 index 000000000..f7ce5a9c4 --- /dev/null +++ b/projectq/setups/ionq_test.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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.setup.ionq.""" + +import pytest + +from projectq.backends._ionq._ionq_exc import DeviceOfflineError +from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper + + +def test_basic_ionq_mapper(monkeypatch): + import projectq.setups.ionq + + def mock_show_devices(*args, **kwargs): + return {'dummy': {'nq': 3, 'target': 'dummy'}} + + monkeypatch.setattr(projectq.setups.ionq, 'show_devices', mock_show_devices) + engine_list = projectq.setups.ionq.get_engine_list(device='dummy') + assert len(engine_list) > 1 + mapper = engine_list[-1] + assert isinstance(mapper, BoundedQubitMapper) + # to match nq in the backend + assert mapper.max_qubits == 3 + + +def test_ionq_errors(monkeypatch): + import projectq.setups.ionq + + def mock_show_devices(*args, **kwargs): + return {'dummy': {'nq': 3, 'target': 'dummy'}} + + monkeypatch.setattr(projectq.setups.ionq, 'show_devices', mock_show_devices) + + with pytest.raises(DeviceOfflineError): + projectq.setups.ionq.get_engine_list(device='simulator') From aa3afaf4b01bd028c19b86869141cede2873d986 Mon Sep 17 00:00:00 2001 From: XYShe <30593841+XYShe@users.noreply.github.com> Date: Wed, 16 Jun 2021 14:28:55 +0200 Subject: [PATCH 09/11] Ctrl generalize (#400) * Init * Apply control through compute engine. Added Enum class * Apply control through compute engine. Added Enum class * Apply control through compute engine, added enum class * Fix small bug due to wrong default value, clean up output * abolished decomp * Revert "abolished decomp" This reverts commit 36743a15b7e3c66a9f648735de91f690eea51121. * Apply Control through decomposition. Added Test Files * Fix a few issues with new control state - Address inconsistency with qubit state ordering when using integers as input control state - Add canonical_ctrl_state function to centralise functionality - Fix some file encoding - Fix tests * Update examples/control_tester.py * Add some missing license headers and fix some tests * Add missing docstring * Cleanup some code in AQT and IBM backends * Some code cleanup in _simulator.py * Change autoreplacer priority for control. Added Additional test for autoreplacer. Added check for canonical ctrl state func. * Update projectq/setups/default.py * Update projectq/setups/decompositions/cnu2toffoliandcu.py * Update projectq/setups/decompositions/cnu2toffoliandcu.py * Cleanup code in _replacer.py * Tweak some of the unit tests + add comments * Add more tests for canonical_ctrl_state and has_negative_control * Short pass of reformatting using black * Bug fixing for rebasing * Reformat files. Improve control_tester examples. Update change log * Dummy change to trigger CI with new state * Use pytest-mock for awsbraket client testing * Fix Linter warnings * Use pytest-mock also for awsbraket backend tests * Fix missing tests in backends and added support for IonQ * Fix linter warning * Add support for AWSBraketBackend * Fix small typo * Use backported mock instead of unittest.mock * Sort requirements_tests.txt * Fix a bunch of errors that happens at program exit Monkeypatching or patching of external may unload the patch before the MainEngine calls the last flush operations which would then call the original API although unwanted. Co-authored-by: Damien Nguyen --- .gitignore | 5 + CHANGELOG.md | 1 + examples/control_tester.py | 89 ++++++++++++++ projectq/backends/_aqt/_aqt_test.py | 10 +- projectq/backends/_awsbraket/_awsbraket.py | 24 ++-- .../_awsbraket_boto3_client_test.py | 45 +++---- .../backends/_awsbraket/_awsbraket_test.py | 54 ++++++--- projectq/backends/_ibm/_ibm.py | 19 +-- projectq/backends/_ibm/_ibm_test.py | 18 ++- projectq/backends/_ionq/_ionq.py | 5 +- projectq/backends/_ionq/_ionq_mapper_test.py | 32 ++--- projectq/backends/_ionq/_ionq_test.py | 20 +++- projectq/backends/_sim/_simulator.py | 16 ++- projectq/backends/_sim/_simulator_test.py | 17 +++ projectq/cengines/_basics.py | 1 + projectq/cengines/_main.py | 2 +- projectq/cengines/_replacer/_replacer.py | 86 ++++++++------ projectq/cengines/_replacer/_replacer_test.py | 48 ++++++++ projectq/meta/__init__.py | 2 +- projectq/meta/_control.py | 72 +++++++++++- projectq/meta/_control_test.py | 111 +++++++++++++++++- projectq/ops/__init__.py | 12 +- projectq/ops/_command.py | 63 +++++++++- projectq/ops/_command_test.py | 43 ++++++- projectq/setups/decompositions/__init__.py | 2 + .../setups/decompositions/controlstate.py | 45 +++++++ .../decompositions/controlstate_test.py | 48 ++++++++ projectq/setups/default.py | 16 +-- pyproject.toml | 1 + requirements_tests.txt | 2 + 30 files changed, 736 insertions(+), 173 deletions(-) create mode 100755 examples/control_tester.py create mode 100755 projectq/setups/decompositions/controlstate.py create mode 100755 projectq/setups/decompositions/controlstate_test.py diff --git a/.gitignore b/.gitignore index 3b349b6a1..680678f56 100644 --- a/.gitignore +++ b/.gitignore @@ -171,6 +171,9 @@ dmypy.json *.out *.app +# Others +err.txt + # ============================================================================== VERSION.txt @@ -180,3 +183,5 @@ thumbs.db # Mac OSX artifacts *.DS_Store + +# ============================================================================== diff --git a/CHANGELOG.md b/CHANGELOG.md index 154ad88ec..7fe324dce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added ``pyproject.toml`` and ``setup.cfg`` - Added CHANGELOG.md - Added backend for IonQ. +- Added support for state-dependent qubit control ### Deprecated diff --git a/examples/control_tester.py b/examples/control_tester.py new file mode 100755 index 000000000..94833a70e --- /dev/null +++ b/examples/control_tester.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. + +from projectq.cengines import MainEngine +from projectq.meta import Control +from projectq.ops import All, X, Measure, CtrlAll + + +def run_circuit(eng, circuit_num): + qubit = eng.allocate_qureg(2) + ctrl_fail = eng.allocate_qureg(3) + ctrl_success = eng.allocate_qureg(3) + + if circuit_num == 1: + with Control(eng, ctrl_fail): + X | qubit[0] + All(X) | ctrl_success + with Control(eng, ctrl_success): + X | qubit[1] + + elif circuit_num == 2: + All(X) | ctrl_fail + with Control(eng, ctrl_fail, ctrl_state=CtrlAll.Zero): + X | qubit[0] + with Control(eng, ctrl_success, ctrl_state=CtrlAll.Zero): + X | qubit[1] + + elif circuit_num == 3: + All(X) | ctrl_fail + with Control(eng, ctrl_fail, ctrl_state='101'): + X | qubit[0] + + X | ctrl_success[0] + X | ctrl_success[2] + with Control(eng, ctrl_success, ctrl_state='101'): + X | qubit[1] + + elif circuit_num == 4: + All(X) | ctrl_fail + with Control(eng, ctrl_fail, ctrl_state=5): + X | qubit[0] + + X | ctrl_success[0] + X | ctrl_success[2] + with Control(eng, ctrl_success, ctrl_state=5): + X | qubit[1] + + All(Measure) | qubit + All(Measure) | ctrl_fail + All(Measure) | ctrl_success + eng.flush() + return qubit, ctrl_fail, ctrl_success + + +if __name__ == '__main__': + # Create a MainEngine with a unitary simulator backend + eng = MainEngine() + + # Run out quantum circuit + # 1 - Default behaviour of the control: all control qubits should be 1 + # 2 - Off-control: all control qubits should remain 0 + # 3 - Specific state given by a string + # 4 - Specific state given by an integer + + qubit, ctrl_fail, ctrl_success = run_circuit(eng, 4) + + # Measured value of the failed qubit should be 0 in all cases + print('The final value of the qubit with failed control is:') + print(int(qubit[0])) + print('with the state of control qubits are:') + print([int(qubit) for qubit in ctrl_fail], '\n') + + # Measured value of the success qubit should be 1 in all cases + print('The final value of the qubit with successful control is:') + print(int(qubit[1])) + print('with the state of control qubits are:') + print([int(qubit) for qubit in ctrl_success], '\n') diff --git a/projectq/backends/_aqt/_aqt_test.py b/projectq/backends/_aqt/_aqt_test.py index 0810cfe0a..a11293853 100644 --- a/projectq/backends/_aqt/_aqt_test.py +++ b/projectq/backends/_aqt/_aqt_test.py @@ -19,7 +19,7 @@ from projectq import MainEngine from projectq.backends._aqt import _aqt -from projectq.types import WeakQubitRef, Qubit +from projectq.types import WeakQubitRef from projectq.cengines import DummyEngine, BasicMapperEngine from projectq.ops import ( All, @@ -140,6 +140,10 @@ def test_aqt_too_many_runs(): Rx(math.pi / 2) | qubit eng.flush() + # Avoid exception at deletion + backend._num_runs = 1 + backend._circuit = [] + def test_aqt_retrieve(monkeypatch): # patch send @@ -171,7 +175,7 @@ def mock_retrieve(*args, **kwargs): assert prob_dict['00'] == pytest.approx(0.6) # Unknown qubit and no mapper - invalid_qubit = [Qubit(eng, 10)] + invalid_qubit = [WeakQubitRef(eng, 10)] with pytest.raises(RuntimeError): eng.backend.get_probabilities(invalid_qubit) @@ -227,7 +231,7 @@ def mock_send(*args, **kwargs): assert prob_dict['00'] == pytest.approx(0.6) # Unknown qubit and no mapper - invalid_qubit = [Qubit(eng, 10)] + invalid_qubit = [WeakQubitRef(eng, 10)] with pytest.raises(RuntimeError): eng.backend.get_probabilities(invalid_qubit) diff --git a/projectq/backends/_awsbraket/_awsbraket.py b/projectq/backends/_awsbraket/_awsbraket.py index 2eaf7ba00..7f158afa1 100755 --- a/projectq/backends/_awsbraket/_awsbraket.py +++ b/projectq/backends/_awsbraket/_awsbraket.py @@ -18,7 +18,7 @@ import json from projectq.cengines import BasicEngine -from projectq.meta import get_control_count, LogicalQubitIDTag +from projectq.meta import get_control_count, LogicalQubitIDTag, has_negative_control from projectq.types import WeakQubitRef from projectq.ops import ( R, @@ -176,6 +176,9 @@ def is_available(self, cmd): if gate in (Measure, Allocate, Deallocate, Barrier): return True + if has_negative_control(cmd): + return False + if self.device == 'Aspen-8': if get_control_count(cmd) == 2: return isinstance(gate, XGate) @@ -271,21 +274,24 @@ def _store(self, cmd): Args: cmd: Command to store """ + gate = cmd.gate + + # Do not clear the self._clear flag for those gates + if gate in (Deallocate, Barrier): + return + + num_controls = get_control_count(cmd) + gate_type = type(gate) if not isinstance(gate, DaggeredGate) else type(gate._gate) + if self._clear: self._probabilities = dict() self._clear = False self._circuit = "" self._allocated_qubits = set() - gate = cmd.gate - num_controls = get_control_count(cmd) - gate_type = type(gate) if not isinstance(gate, DaggeredGate) else type(gate._gate) - if gate == Allocate: self._allocated_qubits.add(cmd.qubits[0][0].id) return - if gate in (Deallocate, Barrier): - return if gate == Measure: assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 qb_id = cmd.qubits[0][0].id @@ -412,6 +418,10 @@ def _run(self): # Also, AWS Braket currently does not support intermediate # measurements. + # If the clear flag is set, nothing to do here... + if self._clear: + return + # In Braket the results for the jobs are stored in S3. # You can recover the results from previous jobs using the TaskArn # (self._retrieve_execution). diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py index 4c669d165..5faf939a8 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py @@ -15,7 +15,6 @@ """ Test for projectq.backends._awsbraket._awsbraket_boto3_client.py """ import pytest -from unittest.mock import patch from ._awsbraket_boto3_client_test_fixtures import * # noqa: F401,F403 @@ -34,13 +33,13 @@ @has_boto3 -@patch('boto3.client') -def test_show_devices(mock_boto3_client, show_devices_setup): +def test_show_devices(mocker, show_devices_setup): creds, search_value, device_value, devicelist_result = show_devices_setup - mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client = mocker.MagicMock(spec=['search_devices', 'get_device']) mock_boto3_client.search_devices.return_value = search_value mock_boto3_client.get_device.return_value = device_value + mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) devicelist = _awsbraket_boto3_client.show_devices(credentials=creds) assert devicelist == devicelist_result @@ -85,7 +84,6 @@ def test_show_devices(mock_boto3_client, show_devices_setup): @has_boto3 -@patch('boto3.client') @pytest.mark.parametrize( "var_status, var_result", [ @@ -95,13 +93,14 @@ def test_show_devices(mock_boto3_client, show_devices_setup): ('other', other_value), ], ) -def test_retrieve(mock_boto3_client, var_status, var_result, retrieve_setup): +def test_retrieve(mocker, var_status, var_result, retrieve_setup): arntask, creds, device_value, res_completed, results_dict = retrieve_setup - mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client = mocker.MagicMock(spec=['get_quantum_task', 'get_device', 'get_object']) mock_boto3_client.get_quantum_task.return_value = var_result mock_boto3_client.get_device.return_value = device_value mock_boto3_client.get_object.return_value = results_dict + mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) if var_status == 'completed': res = _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask) @@ -132,8 +131,7 @@ def test_retrieve(mock_boto3_client, var_status, var_result, retrieve_setup): @has_boto3 -@patch('boto3.client') -def test_retrieve_devicetypes(mock_boto3_client, retrieve_devicetypes_setup): +def test_retrieve_devicetypes(mocker, retrieve_devicetypes_setup): ( arntask, creds, @@ -142,10 +140,11 @@ def test_retrieve_devicetypes(mock_boto3_client, retrieve_devicetypes_setup): res_completed, ) = retrieve_devicetypes_setup - mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client = mocker.MagicMock(spec=['get_quantum_task', 'get_device', 'get_object']) mock_boto3_client.get_quantum_task.return_value = completed_value mock_boto3_client.get_device.return_value = device_value mock_boto3_client.get_object.return_value = results_dict + mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) res = _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask) assert res == res_completed @@ -155,13 +154,13 @@ def test_retrieve_devicetypes(mock_boto3_client, retrieve_devicetypes_setup): @has_boto3 -@patch('boto3.client') -def test_send_too_many_qubits(mock_boto3_client, send_too_many_setup): +def test_send_too_many_qubits(mocker, send_too_many_setup): (creds, s3_folder, search_value, device_value, info_too_much) = send_too_many_setup - mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client = mocker.MagicMock(spec=['search_devices', 'get_device']) mock_boto3_client.search_devices.return_value = search_value mock_boto3_client.get_device.return_value = device_value + mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) with pytest.raises(_awsbraket_boto3_client.DeviceTooSmall): _awsbraket_boto3_client.send(info_too_much, device='name2', credentials=creds, s3_folder=s3_folder) @@ -171,7 +170,6 @@ def test_send_too_many_qubits(mock_boto3_client, send_too_many_setup): @has_boto3 -@patch('boto3.client') @pytest.mark.parametrize( "var_status, var_result", [ @@ -181,7 +179,7 @@ def test_send_too_many_qubits(mock_boto3_client, send_too_many_setup): ('other', other_value), ], ) -def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_result, real_device_online_setup): +def test_send_real_device_online_verbose(mocker, var_status, var_result, real_device_online_setup): ( qtarntask, @@ -194,12 +192,15 @@ def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_resu results_dict, ) = real_device_online_setup - mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client = mocker.MagicMock( + spec=['search_devices', 'get_device', 'create_quantum_task', 'get_quantum_task', 'get_object'] + ) mock_boto3_client.search_devices.return_value = search_value mock_boto3_client.get_device.return_value = device_value mock_boto3_client.create_quantum_task.return_value = qtarntask mock_boto3_client.get_quantum_task.return_value = var_result mock_boto3_client.get_object.return_value = results_dict + mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) # This is a ficticios situation because the job will be always queued # at the beginning. After that the status will change at some point in time @@ -243,7 +244,6 @@ def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_resu @has_boto3 -@patch('boto3.client') @pytest.mark.parametrize( "var_error", [ @@ -254,16 +254,17 @@ def test_send_real_device_online_verbose(mock_boto3_client, var_status, var_resu ('ValidationException'), ], ) -def test_send_that_errors_are_caught(mock_boto3_client, var_error, send_that_error_setup): +def test_send_that_errors_are_caught(mocker, var_error, send_that_error_setup): creds, s3_folder, info, search_value, device_value = send_that_error_setup - mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client = mocker.MagicMock(spec=['search_devices', 'get_device', 'create_quantum_task']) mock_boto3_client.search_devices.return_value = search_value mock_boto3_client.get_device.return_value = device_value mock_boto3_client.create_quantum_task.side_effect = botocore.exceptions.ClientError( {"Error": {"Code": var_error, "Message": "Msg error for " + var_error}}, "create_quantum_task", ) + mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) with pytest.raises(botocore.exceptions.ClientError): _awsbraket_boto3_client.send(info, device='name2', credentials=creds, s3_folder=s3_folder, num_retries=2) @@ -282,15 +283,15 @@ def test_send_that_errors_are_caught(mock_boto3_client, var_error, send_that_err @has_boto3 -@patch('boto3.client') @pytest.mark.parametrize("var_error", [('ResourceNotFoundException')]) -def test_retrieve_error_arn_not_exist(mock_boto3_client, var_error, arntask, creds): +def test_retrieve_error_arn_not_exist(mocker, var_error, arntask, creds): - mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client = mocker.MagicMock(spec=['get_quantum_task']) mock_boto3_client.get_quantum_task.side_effect = botocore.exceptions.ClientError( {"Error": {"Code": var_error, "Message": "Msg error for " + var_error}}, "get_quantum_task", ) + mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) with pytest.raises(botocore.exceptions.ClientError): _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask) diff --git a/projectq/backends/_awsbraket/_awsbraket_test.py b/projectq/backends/_awsbraket/_awsbraket_test.py index d82274cbe..dc51283ab 100644 --- a/projectq/backends/_awsbraket/_awsbraket_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_test.py @@ -15,13 +15,13 @@ """ Test for projectq.backends._awsbraket._awsbraket.py""" import pytest -from unittest.mock import patch import copy import math from projectq import MainEngine -from projectq.types import WeakQubitRef, Qubit + +from projectq.types import WeakQubitRef from projectq.cengines import ( BasicMapperEngine, DummyEngine, @@ -320,6 +320,22 @@ def test_awsbraket_backend_is_available_control_singlequbit_sv1(ctrl_singlequbit assert aws_backend.is_available(cmd) == is_available_sv1 +def test_awsbraket_backend_is_available_negative_control(): + backend = _awsbraket.AWSBraketBackend() + + qb0 = WeakQubitRef(engine=None, idx=0) + qb1 = WeakQubitRef(engine=None, idx=1) + qb2 = WeakQubitRef(engine=None, idx=2) + + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1])) + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='1')) + assert not backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='0')) + + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1, qb2])) + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1, qb2], control_state='11')) + assert not backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1, qb2], control_state='01')) + + @has_boto3 def test_awsbraket_backend_is_available_swap_aspen(): eng = MainEngine(backend=DummyEngine(), engine_list=[DummyEngine()]) @@ -403,18 +419,18 @@ def test_awsbraket_invalid_command(): @has_boto3 -@patch('boto3.client') -def test_awsbraket_sent_error(mock_boto3_client, sent_error_setup): +def test_awsbraket_sent_error(mocker, sent_error_setup): creds, s3_folder, search_value, device_value = sent_error_setup var_error = 'ServiceQuotaExceededException' - mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client = mocker.MagicMock(spec=['search_devices', 'get_device', 'create_quantum_task']) mock_boto3_client.search_devices.return_value = search_value mock_boto3_client.get_device.return_value = device_value mock_boto3_client.create_quantum_task.side_effect = botocore.exceptions.ClientError( {"Error": {"Code": var_error, "Message": "Msg error for " + var_error}}, "create_quantum_task", ) + mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) backend = _awsbraket.AWSBraketBackend( verbose=True, @@ -461,14 +477,14 @@ def test_awsbraket_sent_error_2(): @has_boto3 -@patch('boto3.client') -def test_awsbraket_retrieve(mock_boto3_client, retrieve_setup): +def test_awsbraket_retrieve(mocker, retrieve_setup): (arntask, creds, completed_value, device_value, results_dict) = retrieve_setup - mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client = mocker.MagicMock(spec=['get_quantum_task', 'get_device', 'get_object']) mock_boto3_client.get_quantum_task.return_value = completed_value mock_boto3_client.get_device.return_value = device_value mock_boto3_client.get_object.return_value = results_dict + mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) backend = _awsbraket.AWSBraketBackend(retrieve_execution=arntask, credentials=creds, num_retries=2, verbose=True) @@ -491,7 +507,7 @@ def test_awsbraket_retrieve(mock_boto3_client, retrieve_setup): assert prob_dict['010'] == 0.8 # Unknown qubit or no mapper - invalid_qubit = [Qubit(eng, 10)] + invalid_qubit = [WeakQubitRef(eng, 10)] with pytest.raises(RuntimeError): eng.backend.get_probabilities(invalid_qubit) @@ -500,8 +516,7 @@ def test_awsbraket_retrieve(mock_boto3_client, retrieve_setup): @has_boto3 -@patch('boto3.client') -def test_awsbraket_backend_functional_test(mock_boto3_client, functional_setup, mapper): +def test_awsbraket_backend_functional_test(mocker, functional_setup, mapper): ( creds, s3_folder, @@ -512,12 +527,15 @@ def test_awsbraket_backend_functional_test(mock_boto3_client, functional_setup, results_dict, ) = functional_setup - mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client = mocker.MagicMock( + spec=['search_devices', 'get_device', 'create_quantum_task', 'get_quantum_task', 'get_object'] + ) mock_boto3_client.search_devices.return_value = search_value mock_boto3_client.get_device.return_value = device_value mock_boto3_client.create_quantum_task.return_value = qtarntask mock_boto3_client.get_quantum_task.return_value = completed_value mock_boto3_client.get_object.return_value = results_dict + mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) backend = _awsbraket.AWSBraketBackend( verbose=True, @@ -574,10 +592,11 @@ def test_awsbraket_backend_functional_test(mock_boto3_client, functional_setup, assert prob_dict['00'] == pytest.approx(0.84) assert prob_dict['01'] == pytest.approx(0.06) + eng.flush(deallocate_qubits=True) + @has_boto3 -@patch('boto3.client') -def test_awsbraket_functional_test_as_engine(mock_boto3_client, functional_setup): +def test_awsbraket_functional_test_as_engine(mocker, functional_setup): ( creds, s3_folder, @@ -588,12 +607,15 @@ def test_awsbraket_functional_test_as_engine(mock_boto3_client, functional_setup results_dict, ) = functional_setup - mock_boto3_client.return_value = mock_boto3_client + mock_boto3_client = mocker.MagicMock( + spec=['search_devices', 'get_device', 'create_quantum_task', 'get_quantum_task', 'get_object'] + ) mock_boto3_client.search_devices.return_value = search_value mock_boto3_client.get_device.return_value = device_value mock_boto3_client.create_quantum_task.return_value = qtarntask mock_boto3_client.get_quantum_task.return_value = completed_value mock_boto3_client.get_object.return_value = copy.deepcopy(results_dict) + mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) backend = _awsbraket.AWSBraketBackend( verbose=True, @@ -638,3 +660,5 @@ def test_awsbraket_functional_test_as_engine(mock_boto3_client, functional_setup assert eng.backend.received_commands[7].qubits[0][0].id == qureg[0].id assert eng.backend.received_commands[8].gate == H assert eng.backend.received_commands[8].qubits[0][0].id == qureg[1].id + + eng.flush(deallocate_qubits=True) diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index baa4de60c..c34057817 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -17,19 +17,8 @@ import random from projectq.cengines import BasicEngine -from projectq.meta import get_control_count, LogicalQubitIDTag -from projectq.ops import ( - NOT, - H, - Rx, - Ry, - Rz, - Measure, - Allocate, - Deallocate, - Barrier, - FlushGate, -) +from projectq.meta import get_control_count, LogicalQubitIDTag, has_negative_control +from projectq.ops import NOT, H, Rx, Ry, Rz, Measure, Allocate, Deallocate, Barrier, FlushGate from ._ibm_http_client import send, retrieve @@ -100,7 +89,11 @@ def is_available(self, cmd): Args: cmd (Command): Command for which to check availability """ + if has_negative_control(cmd): + return False + g = cmd.gate + if g == NOT and get_control_count(cmd) == 1: return True if get_control_count(cmd) == 0: diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index 27249161d..204878276 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -16,11 +16,8 @@ import pytest import math -from projectq.setups import restrictedgateset -from projectq import MainEngine from projectq.backends._ibm import _ibm -from projectq.cengines import BasicMapperEngine, DummyEngine - +from projectq.cengines import MainEngine, BasicMapperEngine, DummyEngine from projectq.ops import ( All, Allocate, @@ -43,6 +40,8 @@ H, CNOT, ) +from projectq.setups import restrictedgateset +from projectq.types import WeakQubitRef # Insure that no HTTP request can be made in all tests in this module @@ -91,6 +90,17 @@ def test_ibm_backend_is_available_control_not(num_ctrl_qubits, is_available): assert ibm_backend.is_available(cmd) == is_available +def test_ibm_backend_is_available_negative_control(): + backend = _ibm.IBMBackend() + + qb0 = WeakQubitRef(engine=None, idx=0) + qb1 = WeakQubitRef(engine=None, idx=1) + + assert backend.is_available(Command(None, NOT, qubits=([qb0],), controls=[qb1])) + assert backend.is_available(Command(None, NOT, qubits=([qb0],), controls=[qb1], control_state='1')) + assert not backend.is_available(Command(None, NOT, qubits=([qb0],), controls=[qb1], control_state='0')) + + def test_ibm_backend_init(): backend = _ibm.IBMBackend(verbose=True, use_hardware=True) assert backend.qasm == "" diff --git a/projectq/backends/_ionq/_ionq.py b/projectq/backends/_ionq/_ionq.py index 055eaab61..6dfd52d20 100644 --- a/projectq/backends/_ionq/_ionq.py +++ b/projectq/backends/_ionq/_ionq.py @@ -17,7 +17,7 @@ import random from projectq.cengines import BasicEngine -from projectq.meta import LogicalQubitIDTag, get_control_count +from projectq.meta import LogicalQubitIDTag, get_control_count, has_negative_control from projectq.ops import ( Allocate, Barrier, @@ -143,6 +143,9 @@ def is_available(self, cmd): if gate in (Measure, Allocate, Deallocate, Barrier): return True + if has_negative_control(cmd): + return False + # CNOT gates. # NOTE: IonQ supports up to 7 control qubits num_ctrl_qubits = get_control_count(cmd) diff --git a/projectq/backends/_ionq/_ionq_mapper_test.py b/projectq/backends/_ionq/_ionq_mapper_test.py index bf5784984..4f7e70605 100644 --- a/projectq/backends/_ionq/_ionq_mapper_test.py +++ b/projectq/backends/_ionq/_ionq_mapper_test.py @@ -14,18 +14,19 @@ # limitations under the License. import pytest -from projectq import MainEngine from projectq.backends import Simulator from projectq.backends._ionq._ionq_mapper import BoundedQubitMapper +from projectq.cengines import MainEngine, DummyEngine from projectq.meta import LogicalQubitIDTag from projectq.ops import AllocateQubitGate, Command, DeallocateQubitGate from projectq.types import WeakQubitRef def test_cannot_allocate_past_max(): + mapper = BoundedQubitMapper(1) engine = MainEngine( - Simulator(), - engine_list=[BoundedQubitMapper(1)], + DummyEngine(), + engine_list=[mapper], verbose=True, ) engine.allocate_qubit() @@ -34,6 +35,9 @@ def test_cannot_allocate_past_max(): assert str(excinfo.value) == "Cannot allocate more than 1 qubits!" + # Avoid double error reporting + mapper.current_mapping = {0: 0, 1: 1} + def test_cannot_reallocate_same_qubit(): engine = MainEngine( @@ -74,26 +78,22 @@ def test_cannot_deallocate_unknown_qubit(): assert str(excinfo.value) == "Cannot deallocate a qubit that is not already allocated!" # but we can still deallocate an already allocated one - qubit_id = qureg[0].id - deallocate_cmd = Command( - engine=engine, - gate=DeallocateQubitGate(), - qubits=([WeakQubitRef(engine=engine, idx=qubit_id)],), - tags=[LogicalQubitIDTag(qubit_id)], - ) - engine.send([deallocate_cmd]) + engine.deallocate_qubit(qureg[0]) + del qureg + del engine def test_cannot_deallocate_same_qubit(): + mapper = BoundedQubitMapper(1) engine = MainEngine( Simulator(), - engine_list=[BoundedQubitMapper(1)], + engine_list=[mapper], verbose=True, ) qureg = engine.allocate_qubit() - qubit = qureg[0] - qubit_id = qubit.id - engine.deallocate_qubit(qubit) + qubit_id = qureg[0].id + engine.deallocate_qubit(qureg[0]) + with pytest.raises(RuntimeError) as excinfo: deallocate_cmd = Command( engine=engine, @@ -106,7 +106,7 @@ def test_cannot_deallocate_same_qubit(): assert str(excinfo.value) == "Cannot deallocate a qubit that is not already allocated!" -def test_flush_deallocates_all_qubits(monkeypatch): +def test_flush_deallocates_all_qubits(): mapper = BoundedQubitMapper(10) engine = MainEngine( Simulator(), diff --git a/projectq/backends/_ionq/_ionq_test.py b/projectq/backends/_ionq/_ionq_test.py index 46458e603..4156edb63 100644 --- a/projectq/backends/_ionq/_ionq_test.py +++ b/projectq/backends/_ionq/_ionq_test.py @@ -54,7 +54,7 @@ Y, Z, ) -from projectq.types import Qubit, WeakQubitRef +from projectq.types import WeakQubitRef @pytest.fixture(scope='function') @@ -126,6 +126,22 @@ def test_ionq_backend_is_available_control_not(num_ctrl_qubits, is_available): assert ionq_backend.is_available(cmd) is is_available +def test_ionq_backend_is_available_negative_control(): + backend = _ionq.IonQBackend() + + qb0 = WeakQubitRef(engine=None, idx=0) + qb1 = WeakQubitRef(engine=None, idx=1) + qb2 = WeakQubitRef(engine=None, idx=2) + + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1])) + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='1')) + assert not backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='0')) + + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1, qb2])) + assert backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1, qb2], control_state='11')) + assert not backend.is_available(Command(None, X, qubits=([qb0],), controls=[qb1, qb2], control_state='01')) + + def test_ionq_backend_init(): """Test initialized backend has an empty circuit""" backend = _ionq.IonQBackend(verbose=True, use_hardware=True) @@ -331,7 +347,7 @@ def mock_retrieve(*args, **kwargs): assert prob_dict['00'] == pytest.approx(0.6) # Unknown qubit - invalid_qubit = [Qubit(eng, 10)] + invalid_qubit = [WeakQubitRef(eng, 10)] probs = eng.backend.get_probabilities(invalid_qubit) assert {'0': 1} == probs diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index 4aca230f6..3647136b6 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -21,15 +21,8 @@ import math import random from projectq.cengines import BasicEngine -from projectq.meta import get_control_count, LogicalQubitIDTag -from projectq.ops import ( - Measure, - FlushGate, - Allocate, - Deallocate, - BasicMathGate, - TimeEvolution, -) +from projectq.meta import get_control_count, LogicalQubitIDTag, has_negative_control +from projectq.ops import Measure, FlushGate, Allocate, Deallocate, BasicMathGate, TimeEvolution from projectq.types import WeakQubitRef FALLBACK_TO_PYSIM = False @@ -104,6 +97,9 @@ def is_available(self, cmd): Returns: True if it can be simulated and False otherwise. """ + if has_negative_control(cmd): + return False + if ( cmd.gate == Measure or cmd.gate == Allocate @@ -352,6 +348,7 @@ def _handle(self, cmd): Exception: If a non-single-qubit gate needs to be processed (which should never happen due to is_available). """ + if cmd.gate == Measure: assert get_control_count(cmd) == 0 ids = [qb.id for qr in cmd.qubits for qb in qr] @@ -428,6 +425,7 @@ def _handle(self, cmd): ) ) self._simulator.apply_controlled_gate(matrix.tolist(), ids, [qb.id for qb in cmd.control_qubits]) + if not self._gate_fusion: self._simulator.run() else: diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 0b7f6d288..0d3cae90f 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -180,6 +180,20 @@ def test_simulator_is_available(sim): assert new_cmd.gate.cnt == 0 +def test_simulator_is_available_negative_control(sim): + qb0 = WeakQubitRef(engine=None, idx=0) + qb1 = WeakQubitRef(engine=None, idx=1) + qb2 = WeakQubitRef(engine=None, idx=2) + + assert sim.is_available(Command(None, X, qubits=([qb0],), controls=[qb1])) + assert sim.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='1')) + assert not sim.is_available(Command(None, X, qubits=([qb0],), controls=[qb1], control_state='0')) + + assert sim.is_available(Command(None, X, qubits=([qb0],), controls=[qb1, qb2])) + assert sim.is_available(Command(None, X, qubits=([qb0],), controls=[qb1, qb2], control_state='11')) + assert not sim.is_available(Command(None, X, qubits=([qb0],), controls=[qb1, qb2], control_state='01')) + + def test_simulator_cheat(sim): # cheat function should return a tuple assert isinstance(sim.cheat(), tuple) @@ -490,6 +504,9 @@ def test_simulator_applyqubitoperator(sim, mapper): sim.apply_qubit_operator(op_Proj1, [qureg[0]]) assert sim.get_amplitude('000', qureg) == pytest.approx(0.0) + # TODO: this is suspicious... + eng.backend.set_wavefunction([1, 0, 0, 0, 0, 0, 0, 0], qureg) + def test_simulator_time_evolution(sim): N = 8 # number of qubits diff --git a/projectq/cengines/_basics.py b/projectq/cengines/_basics.py index 6a6f6c79d..9ac53bb6f 100755 --- a/projectq/cengines/_basics.py +++ b/projectq/cengines/_basics.py @@ -196,6 +196,7 @@ def send(self, command_list): """ Forward the list of commands to the next engine in the pipeline. """ + self.next_engine.receive(command_list) diff --git a/projectq/cengines/_main.py b/projectq/cengines/_main.py index 3eaea3ef8..475207dd1 100755 --- a/projectq/cengines/_main.py +++ b/projectq/cengines/_main.py @@ -300,7 +300,7 @@ def flush(self, deallocate_qubits=False): id to -1). """ if deallocate_qubits: - while len(self.active_qubits): + while [qb for qb in self.active_qubits if qb is not None]: qb = self.active_qubits.pop() qb.__del__() self.receive([Command(self, FlushGate(), ([WeakQubitRef(self, -1)],))]) diff --git a/projectq/cengines/_replacer/_replacer.py b/projectq/cengines/_replacer/_replacer.py index 0ad153297..07ea5d3fb 100755 --- a/projectq/cengines/_replacer/_replacer.py +++ b/projectq/cengines/_replacer/_replacer.py @@ -126,10 +126,6 @@ def _process_command(self, cmd): if self.is_available(cmd): self.send([cmd]) else: - # check for decomposition rules - decomp_list = [] - potential_decomps = [] - # First check for a decomposition rules of the gate class, then # the gate class of the inverse gate. If nothing is found, do the # same for the first parent class, etc. @@ -138,40 +134,54 @@ def _process_command(self, cmd): # DaggeredGate, BasicGate, object. Hence don't check the last two inverse_mro = type(get_inverse(cmd.gate)).mro()[:-2] rules = self.decompositionRuleSet.decompositions - for level in range(max(len(gate_mro), len(inverse_mro))): - # Check for forward rules - if level < len(gate_mro): - class_name = gate_mro[level].__name__ - try: - potential_decomps = [d for d in rules[class_name]] - except KeyError: - pass - # throw out the ones which don't recognize the command - for d in potential_decomps: - if d.check(cmd): - decomp_list.append(d) - if len(decomp_list) != 0: - break - # Check for rules implementing the inverse gate - # and run them in reverse - if level < len(inverse_mro): - inv_class_name = inverse_mro[level].__name__ - try: - potential_decomps += [d.get_inverse_decomposition() for d in rules[inv_class_name]] - except KeyError: - pass - # throw out the ones which don't recognize the command - for d in potential_decomps: - if d.check(cmd): - decomp_list.append(d) - if len(decomp_list) != 0: - break - - if len(decomp_list) == 0: - raise NoGateDecompositionError("\nNo replacement found for " + str(cmd) + "!") - - # use decomposition chooser to determine the best decomposition - chosen_decomp = self._decomp_chooser(cmd, decomp_list) + + # If the decomposition rule to remove negatively controlled qubits is present in the list of potential + # decompositions, we process it immediately, before any other decompositions. + controlstate_rule = [ + rule for rule in rules.get('BasicGate', []) if rule.decompose.__name__ == '_decompose_controlstate' + ] + if controlstate_rule and controlstate_rule[0].check(cmd): + chosen_decomp = controlstate_rule[0] + else: + # check for decomposition rules + decomp_list = [] + potential_decomps = [] + + for level in range(max(len(gate_mro), len(inverse_mro))): + # Check for forward rules + if level < len(gate_mro): + class_name = gate_mro[level].__name__ + try: + potential_decomps = [d for d in rules[class_name]] + except KeyError: + pass + # throw out the ones which don't recognize the command + for d in potential_decomps: + if d.check(cmd): + decomp_list.append(d) + if len(decomp_list) != 0: + break + # Check for rules implementing the inverse gate + # and run them in reverse + if level < len(inverse_mro): + inv_class_name = inverse_mro[level].__name__ + try: + potential_decomps += [d.get_inverse_decomposition() for d in rules[inv_class_name]] + except KeyError: + pass + # throw out the ones which don't recognize the command + for d in potential_decomps: + if d.check(cmd): + decomp_list.append(d) + if len(decomp_list) != 0: + break + + if len(decomp_list) == 0: + raise NoGateDecompositionError("\nNo replacement found for " + str(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/cengines/_replacer/_replacer_test.py b/projectq/cengines/_replacer/_replacer_test.py index a63e43c87..b2675f191 100755 --- a/projectq/cengines/_replacer/_replacer_test.py +++ b/projectq/cengines/_replacer/_replacer_test.py @@ -23,6 +23,7 @@ ClassicalInstructionGate, Command, H, + S, NotInvertible, Rx, X, @@ -232,3 +233,50 @@ def test_gate_filter_func(self, cmd): eng.flush() received_gate = backend.received_commands[1].gate assert received_gate == X or received_gate == H + + +def test_auto_replacer_priorize_controlstate_rule(): + # Check that when a control state is given and it has negative control, + # Autoreplacer prioritizes the corresponding decomposition rule before anything else. + # (Decomposition rule should have name _decompose_controlstate) + + # Create test gate and inverse + class ControlGate(BasicGate): + pass + + def _decompose_controlstate(cmd): + S | cmd.qubits + + def _decompose_random(cmd): + H | cmd.qubits + + def control_filter(self, cmd): + if cmd.gate == ControlGate(): + return False + return True + + rule_set.add_decomposition_rule(DecompositionRule(BasicGate, _decompose_random)) + + backend = DummyEngine(save_commands=True) + eng = MainEngine( + backend=backend, engine_list=[_replacer.AutoReplacer(rule_set), _replacer.InstructionFilter(control_filter)] + ) + assert len(backend.received_commands) == 0 + qb = eng.allocate_qubit() + ControlGate() | qb + eng.flush() + assert len(backend.received_commands) == 3 + assert backend.received_commands[1].gate == H + + rule_set.add_decomposition_rule(DecompositionRule(BasicGate, _decompose_controlstate)) + + backend = DummyEngine(save_commands=True) + eng = MainEngine( + backend=backend, engine_list=[_replacer.AutoReplacer(rule_set), _replacer.InstructionFilter(control_filter)] + ) + assert len(backend.received_commands) == 0 + qb = eng.allocate_qubit() + ControlGate() | qb + eng.flush() + assert len(backend.received_commands) == 3 + assert backend.received_commands[1].gate == S diff --git a/projectq/meta/__init__.py b/projectq/meta/__init__.py index adb5719e8..ce321f0f0 100755 --- a/projectq/meta/__init__.py +++ b/projectq/meta/__init__.py @@ -25,7 +25,7 @@ from ._dirtyqubit import DirtyQubitTag from ._loop import LoopTag, Loop from ._compute import Compute, Uncompute, CustomUncompute, ComputeTag, UncomputeTag -from ._control import Control, get_control_count +from ._control import Control, get_control_count, has_negative_control, canonical_ctrl_state from ._dagger import Dagger from ._util import insert_engine, drop_engine_after from ._logicalqubit import LogicalQubitIDTag diff --git a/projectq/meta/_control.py b/projectq/meta/_control.py index b50848574..704c76374 100755 --- a/projectq/meta/_control.py +++ b/projectq/meta/_control.py @@ -28,6 +28,61 @@ from projectq.ops import ClassicalInstructionGate from projectq.types import BasicQubit from ._util import insert_engine, drop_engine_after +from projectq.ops import CtrlAll + + +def canonical_ctrl_state(ctrl_state, num_qubits): + """ + Return canonical form for control state + + Args: + ctrl_state (int,str,CtrlAll): Initial control state representation + num_qubits (int): number of control qubits + + Returns: + Canonical form of control state (currently a string composed of '0' and '1') + + Note: + In case of integer values for `ctrl_state`, the least significant bit applies to the first qubit in the qubit + register, e.g. if ctrl_state == 2, its binary representation if '10' with the least significan bit being 0. + This means in particular that the followings are equivalent: + + .. code-block:: python + + canonical_ctrl_state(6, 3) == canonical_ctrl_state(6, '110') + """ + if not num_qubits: + return '' + + if isinstance(ctrl_state, CtrlAll): + if ctrl_state == CtrlAll.One: + return '1' * num_qubits + return '0' * num_qubits + + if isinstance(ctrl_state, int): + # If the user inputs an integer, convert it to binary bit string + converted_str = '{0:b}'.format(ctrl_state).zfill(num_qubits)[::-1] + if len(converted_str) != num_qubits: + raise ValueError( + 'Control state specified as {} ({}) is higher than maximum for {} qubits: {}'.format( + ctrl_state, converted_str, num_qubits, 2 ** num_qubits - 1 + ) + ) + return converted_str + + if isinstance(ctrl_state, str): + # If the user inputs bit string, directly use it + if len(ctrl_state) != num_qubits: + raise ValueError( + 'Control state {} has different length than the number of control qubits {}'.format( + ctrl_state, num_qubits + ) + ) + if not set(ctrl_state).issubset({'0', '1'}): + raise ValueError('Control state {} has string other than 1 and 0'.format(ctrl_state)) + return ctrl_state + + raise TypeError('Input must be a string, an integer or an enum value of class State') class ControlEngine(BasicEngine): @@ -35,7 +90,7 @@ class ControlEngine(BasicEngine): Adds control qubits to all commands that have no compute / uncompute tags. """ - def __init__(self, qubits): + def __init__(self, qubits, ctrl_state=CtrlAll.One): """ Initialize the control engine. @@ -45,6 +100,7 @@ def __init__(self, qubits): """ BasicEngine.__init__(self) self._qubits = qubits + self._state = ctrl_state def _has_compute_uncompute_tag(self, cmd): """ @@ -60,7 +116,7 @@ def _has_compute_uncompute_tag(self, cmd): def _handle_command(self, cmd): if not self._has_compute_uncompute_tag(cmd) and not isinstance(cmd.gate, ClassicalInstructionGate): - cmd.add_control_qubits(self._qubits) + cmd.add_control_qubits(self._qubits, self._state) self.send([cmd]) def receive(self, command_list): @@ -79,7 +135,7 @@ class Control(object): do_something(otherqubits) """ - def __init__(self, engine, qubits): + def __init__(self, engine, qubits, ctrl_state=CtrlAll.One): """ Enter a controlled section. @@ -99,10 +155,11 @@ def __init__(self, engine, qubits): if isinstance(qubits, BasicQubit): qubits = [qubits] self._qubits = qubits + self._state = canonical_ctrl_state(ctrl_state, len(self._qubits)) def __enter__(self): if len(self._qubits) > 0: - ce = ControlEngine(self._qubits) + ce = ControlEngine(self._qubits, self._state) insert_engine(self.engine, ce) def __exit__(self, type, value, traceback): @@ -116,3 +173,10 @@ def get_control_count(cmd): Return the number of control qubits of the command object cmd """ return len(cmd.control_qubits) + + +def has_negative_control(cmd): + """ + Returns whether a command has negatively controlled qubits + """ + return get_control_count(cmd) > 0 and '0' in cmd.control_state diff --git a/projectq/meta/_control_test.py b/projectq/meta/_control_test.py index 601252e04..cc79c4591 100755 --- a/projectq/meta/_control_test.py +++ b/projectq/meta/_control_test.py @@ -13,13 +13,64 @@ # See the License for the specific language governing permissions and # limitations under the License. """Tests for projectq.meta._control.py""" +import pytest from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import Command, H, Rx +from projectq.ops import Command, H, Rx, CtrlAll, X, IncompatibleControlState from projectq.meta import DirtyQubitTag, ComputeTag, UncomputeTag, Compute, Uncompute from projectq.meta import _control +from projectq.types import WeakQubitRef + + +def test_canonical_representation(): + assert _control.canonical_ctrl_state(0, 0) == '' + for num_qubits in range(4): + assert _control.canonical_ctrl_state(0, num_qubits) == '0' * num_qubits + + num_qubits = 4 + for i in range(2 ** num_qubits): + state = '{0:0b}'.format(i).zfill(num_qubits) + assert _control.canonical_ctrl_state(i, num_qubits) == state[::-1] + assert _control.canonical_ctrl_state(state, num_qubits) == state + + for num_qubits in range(10): + assert _control.canonical_ctrl_state(CtrlAll.Zero, num_qubits) == '0' * num_qubits + assert _control.canonical_ctrl_state(CtrlAll.One, num_qubits) == '1' * num_qubits + + with pytest.raises(TypeError): + _control.canonical_ctrl_state(1.1, 2) + + with pytest.raises(ValueError): + _control.canonical_ctrl_state('1', 2) + + with pytest.raises(ValueError): + _control.canonical_ctrl_state('11111', 2) + + with pytest.raises(ValueError): + _control.canonical_ctrl_state('1a', 2) + + with pytest.raises(ValueError): + _control.canonical_ctrl_state(4, 2) + + +def test_has_negative_control(): + qubit0 = WeakQubitRef(None, 0) + qubit1 = WeakQubitRef(None, 0) + qubit2 = WeakQubitRef(None, 0) + qubit3 = WeakQubitRef(None, 0) + assert not _control.has_negative_control(Command(None, H, ([qubit0],))) + assert not _control.has_negative_control(Command(None, H, ([qubit0],), [qubit1])) + assert not _control.has_negative_control(Command(None, H, ([qubit0],), [qubit1], control_state=CtrlAll.One)) + assert _control.has_negative_control(Command(None, H, ([qubit0],), [qubit1], control_state=CtrlAll.Zero)) + assert _control.has_negative_control( + Command(None, H, ([qubit0],), [qubit1, qubit2, qubit3], control_state=CtrlAll.Zero) + ) + assert not _control.has_negative_control( + Command(None, H, ([qubit0],), [qubit1, qubit2, qubit3], control_state='111') + ) + assert _control.has_negative_control(Command(None, H, ([qubit0],), [qubit1, qubit2, qubit3], control_state='101')) def test_control_engine_has_compute_tag(): @@ -31,7 +82,7 @@ def test_control_engine_has_compute_tag(): test_cmd0.tags = [DirtyQubitTag(), ComputeTag(), DirtyQubitTag()] test_cmd1.tags = [DirtyQubitTag(), UncomputeTag(), DirtyQubitTag()] test_cmd2.tags = [DirtyQubitTag()] - control_eng = _control.ControlEngine("MockEng") + control_eng = _control.ControlEngine("MockEng", ctrl_state=CtrlAll.One) assert control_eng._has_compute_uncompute_tag(test_cmd0) assert control_eng._has_compute_uncompute_tag(test_cmd1) assert not control_eng._has_compute_uncompute_tag(test_cmd2) @@ -62,3 +113,59 @@ def test_control(): assert backend.received_commands[4].control_qubits[0].id == qureg[0].id assert backend.received_commands[4].control_qubits[1].id == qureg[1].id assert backend.received_commands[6].control_qubits[0].id == qureg[0].id + + +def test_control_state(): + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[DummyEngine()]) + + qureg = eng.allocate_qureg(3) + xreg = eng.allocate_qureg(3) + X | qureg[1] + with _control.Control(eng, qureg[0], '0'): + with Compute(eng): + X | xreg[0] + + X | xreg[1] + Uncompute(eng) + + with _control.Control(eng, qureg[1:], 2): + X | xreg[2] + eng.flush() + + assert len(backend.received_commands) == 6 + 5 + 1 + assert len(backend.received_commands[0].control_qubits) == 0 + assert len(backend.received_commands[1].control_qubits) == 0 + assert len(backend.received_commands[2].control_qubits) == 0 + assert len(backend.received_commands[3].control_qubits) == 0 + assert len(backend.received_commands[4].control_qubits) == 0 + assert len(backend.received_commands[5].control_qubits) == 0 + + assert len(backend.received_commands[6].control_qubits) == 0 + assert len(backend.received_commands[7].control_qubits) == 0 + assert len(backend.received_commands[8].control_qubits) == 1 + assert len(backend.received_commands[9].control_qubits) == 0 + assert len(backend.received_commands[10].control_qubits) == 2 + + assert len(backend.received_commands[11].control_qubits) == 0 + + assert backend.received_commands[8].control_qubits[0].id == qureg[0].id + assert backend.received_commands[8].control_state == '0' + assert backend.received_commands[10].control_qubits[0].id == qureg[1].id + assert backend.received_commands[10].control_qubits[1].id == qureg[2].id + assert backend.received_commands[10].control_state == '01' + + assert _control.has_negative_control(backend.received_commands[8]) + assert _control.has_negative_control(backend.received_commands[10]) + + +def test_control_state_contradiction(): + backend = DummyEngine(save_commands=True) + eng = MainEngine(backend=backend, engine_list=[DummyEngine()]) + qureg = eng.allocate_qureg(1) + with pytest.raises(IncompatibleControlState): + with _control.Control(eng, qureg[0], '0'): + qubit = eng.allocate_qubit() + with _control.Control(eng, qureg[0], '1'): + H | qubit + eng.flush() diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 79f50e32b..21e4e4031 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -25,16 +25,8 @@ BasicMathGate, BasicPhaseGate, ) -from ._command import apply_command, Command -from ._metagates import ( - DaggeredGate, - get_inverse, - is_identity, - ControlledGate, - C, - Tensor, - All, -) +from ._command import apply_command, Command, CtrlAll, IncompatibleControlState +from ._metagates import DaggeredGate, get_inverse, is_identity, ControlledGate, C, Tensor, All from ._gates import * from ._qftgate import QFT, QFTGate from ._qubit_operator import QubitOperator diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 626b0b233..8cd4061d4 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -40,9 +40,24 @@ """ from copy import deepcopy +import itertools import projectq from projectq.types import WeakQubitRef, Qureg +from enum import IntEnum + + +class IncompatibleControlState(Exception): + """ + Exception thrown when trying to set two incompatible states for a control qubit. + """ + + pass + + +class CtrlAll(IntEnum): + Zero = 0 + One = 1 def apply_command(cmd): @@ -84,7 +99,7 @@ class Command(object): all_qubits: A tuple of control_qubits + qubits """ - def __init__(self, engine, gate, qubits, controls=(), tags=()): + def __init__(self, engine, gate, qubits, controls=(), tags=(), control_state=CtrlAll.One): """ Initialize a Command object. @@ -106,6 +121,8 @@ def __init__(self, engine, gate, qubits, controls=(), tags=()): Qubits that condition the command. tags (list[object]): Tags associated with the command. + control_state(int,str,projectq.meta.CtrlAll) + Control state for any control qubits """ qubits = tuple([WeakQubitRef(qubit.engine, qubit.id) for qubit in qreg] for qreg in qubits) @@ -115,6 +132,7 @@ def __init__(self, engine, gate, qubits, controls=(), tags=()): self.qubits = qubits # property self.control_qubits = controls # property self.engine = engine # property + self.control_state = control_state # property @property def qubits(self): @@ -235,7 +253,24 @@ def control_qubits(self, qubits): self._control_qubits = [WeakQubitRef(qubit.engine, qubit.id) for qubit in qubits] self._control_qubits = sorted(self._control_qubits, key=lambda x: x.id) - def add_control_qubits(self, qubits): + @property + def control_state(self): + return self._control_state + + @control_state.setter + def control_state(self, state): + """ + Set control_state to state + + Args: + state (int,str,projectq.meta.CtrtAll): state of control qubit (ie. positive or negative) + """ + # NB: avoid circular imports + from projectq.meta import canonical_ctrl_state + + self._control_state = canonical_ctrl_state(state, len(self._control_qubits)) + + def add_control_qubits(self, qubits, state=CtrlAll.One): """ Add (additional) control qubits to this command object. @@ -244,13 +279,29 @@ def add_control_qubits(self, qubits): thus early deallocation of qubits. Args: - qubits (list of Qubit objects): List of qubits which control this - gate, i.e., the gate is only executed if all qubits are - in state 1. + qubits (list of Qubit objects): List of qubits which control this gate + state (int,str,CtrlAll): Control state (ie. positive or negative) for the qubits being added as + control qubits. """ + # NB: avoid circular imports + from projectq.meta import canonical_ctrl_state + assert isinstance(qubits, list) self._control_qubits.extend([WeakQubitRef(qubit.engine, qubit.id) for qubit in qubits]) - self._control_qubits = sorted(self._control_qubits, key=lambda x: x.id) + self._control_state += canonical_ctrl_state(state, len(qubits)) + + zipped = sorted(zip(self._control_qubits, self._control_state), key=lambda x: x[0].id) + unzipped_qubit, unzipped_state = zip(*zipped) + self._control_qubits, self._control_state = list(unzipped_qubit), ''.join(unzipped_state) + + # Make sure that we do not have contradicting control states for any control qubits + for _, data in itertools.groupby(zipped, key=lambda x: x[0].id): + qubits, states = list(zip(*data)) + assert len(set(qubits)) == 1 # This should be by design... + if len(set(states)) != 1: + raise IncompatibleControlState( + 'Control qubits {} cannot have conflicting control states: {}'.format(list(qubits), states) + ) @property def all_qubits(self): diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index d4823df66..eb4cb6681 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -21,8 +21,8 @@ from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.meta import ComputeTag -from projectq.ops import BasicGate, Rx, NotMergeable +from projectq.meta import ComputeTag, canonical_ctrl_state +from projectq.ops import BasicGate, Rx, NotMergeable, CtrlAll from projectq.types import Qubit, Qureg, WeakQubitRef from projectq.ops import _command @@ -183,14 +183,45 @@ def test_command_interchangeable_qubit_indices(main_engine): ) -def test_commmand_add_control_qubits(main_engine): +@pytest.mark.parametrize( + 'state', + [0, 1, '0', '1', CtrlAll.One, CtrlAll.Zero], + ids=['int(0)', 'int(1)', 'str(0)', 'str(1)', 'CtrlAll.One', 'CtrlAll.Zero'], +) +def test_commmand_add_control_qubits_one(main_engine, state): qubit0 = Qureg([Qubit(main_engine, 0)]) qubit1 = Qureg([Qubit(main_engine, 1)]) - qubit2 = Qureg([Qubit(main_engine, 2)]) cmd = _command.Command(main_engine, Rx(0.5), (qubit0,)) - cmd.add_control_qubits(qubit2 + qubit1) + cmd.add_control_qubits(qubit1, state=state) + assert cmd.control_qubits[0].id == 1 + assert cmd.control_state == canonical_ctrl_state(state, 1) + + +@pytest.mark.parametrize( + 'state', + [0, 1, 2, 3, '00', '01', '10', '11', CtrlAll.One, CtrlAll.Zero], + ids=[ + 'int(0)', + 'int(1)', + 'int(2)', + 'int(3)', + 'str(00)', + 'str(01)', + 'str(10)', + 'str(1)', + 'CtrlAll.One', + 'CtrlAll.Zero', + ], +) +def test_commmand_add_control_qubits_two(main_engine, state): + qubit0 = Qureg([Qubit(main_engine, 0)]) + qubit1 = Qureg([Qubit(main_engine, 1)]) + qubit2 = Qureg([Qubit(main_engine, 2)]) + qubit3 = Qureg([Qubit(main_engine, 3)]) + cmd = _command.Command(main_engine, Rx(0.5), (qubit0,), qubit1) + cmd.add_control_qubits(qubit2 + qubit3, state) assert cmd.control_qubits[0].id == 1 - assert cmd.control_qubits[1].id == 2 + assert cmd.control_state == '1' + canonical_ctrl_state(state, 2) def test_command_all_qubits(main_engine): diff --git a/projectq/setups/decompositions/__init__.py b/projectq/setups/decompositions/__init__.py index 5fabb8dbd..cca7d08c9 100755 --- a/projectq/setups/decompositions/__init__.py +++ b/projectq/setups/decompositions/__init__.py @@ -21,6 +21,7 @@ cnot2rxx, cnot2cz, cnu2toffoliandcu, + controlstate, entangle, globalphase, h2rx, @@ -51,6 +52,7 @@ cnot2rxx, cnot2cz, cnu2toffoliandcu, + controlstate, entangle, globalphase, h2rx, diff --git a/projectq/setups/decompositions/controlstate.py b/projectq/setups/decompositions/controlstate.py new file mode 100755 index 000000000..26a3e5ae7 --- /dev/null +++ b/projectq/setups/decompositions/controlstate.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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. + +""" +Register a decomposition to replace turn negatively controlled qubits into positively controlled qubits by applying X +gates. +""" + +from copy import deepcopy +from projectq.cengines import DecompositionRule +from projectq.meta import Compute, Uncompute, has_negative_control +from projectq.ops import BasicGate, X + + +def _decompose_controlstate(cmd): + """ + Decompose commands with control qubits in negative state (ie. control + qubits with state '0' instead of '1') + """ + with Compute(cmd.engine): + for state, ctrl in zip(cmd.control_state, cmd.control_qubits): + if state == '0': + X | ctrl + + # Resend the command with the `control_state` cleared + cmd.ctrl_state = '1' * len(cmd.control_state) + orig_engine = cmd.engine + cmd.engine.receive([deepcopy(cmd)]) # NB: deepcopy required here to workaround infinite recursion detection + Uncompute(orig_engine) + + +#: Decomposition rules +all_defined_decomposition_rules = [DecompositionRule(BasicGate, _decompose_controlstate, has_negative_control)] diff --git a/projectq/setups/decompositions/controlstate_test.py b/projectq/setups/decompositions/controlstate_test.py new file mode 100755 index 000000000..a74538b58 --- /dev/null +++ b/projectq/setups/decompositions/controlstate_test.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 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 the controlstate decomposition rule. +""" + +from projectq import MainEngine +from projectq.cengines import DummyEngine, AutoReplacer, InstructionFilter, DecompositionRuleSet +from projectq.meta import Control, has_negative_control +from projectq.ops import X +from projectq.setups.decompositions import controlstate, cnot2cz + + +def filter_func(eng, cmd): + if has_negative_control(cmd): + return False + return True + + +def test_controlstate_priority(): + saving_backend = DummyEngine(save_commands=True) + rule_set = DecompositionRuleSet(modules=[cnot2cz, controlstate]) + eng = MainEngine(backend=saving_backend, engine_list=[AutoReplacer(rule_set), InstructionFilter(filter_func)]) + qubit1 = eng.allocate_qubit() + qubit2 = eng.allocate_qubit() + qubit3 = eng.allocate_qubit() + with Control(eng, qubit2, ctrl_state='0'): + X | qubit1 + with Control(eng, qubit3, ctrl_state='1'): + X | qubit1 + eng.flush() + + assert len(saving_backend.received_commands) == 8 + for cmd in saving_backend.received_commands: + assert not has_negative_control(cmd) diff --git a/projectq/setups/default.py b/projectq/setups/default.py index 8f9edeb30..b31d98fcf 100755 --- a/projectq/setups/default.py +++ b/projectq/setups/default.py @@ -12,6 +12,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + """ Defines the default setup which provides an `engine_list` for the `MainEngine` @@ -21,20 +22,9 @@ import projectq import projectq.setups.decompositions -from projectq.cengines import ( - TagRemover, - LocalOptimizer, - AutoReplacer, - DecompositionRuleSet, -) +from projectq.cengines import TagRemover, LocalOptimizer, AutoReplacer, DecompositionRuleSet def get_engine_list(): rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) - return [ - TagRemover(), - LocalOptimizer(10), - AutoReplacer(rule_set), - TagRemover(), - LocalOptimizer(10), - ] + return [TagRemover(), LocalOptimizer(10), AutoReplacer(rule_set), TagRemover(), LocalOptimizer(10)] diff --git a/pyproject.toml b/pyproject.toml index c7034d6fe..e2d959ca0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,7 @@ build-backend = "setuptools.build_meta" minversion = '6.0' addopts = '-pno:warnings' testpaths = ['projectq'] +mock_use_standalone_module = true [tool.setuptools_scm] diff --git a/requirements_tests.txt b/requirements_tests.txt index ab10bc7de..ea01acbbc 100644 --- a/requirements_tests.txt +++ b/requirements_tests.txt @@ -1,3 +1,5 @@ flaky +mock pytest >= 6.0 pytest-cov +pytest-mock From f93cca374c74ca62405f3ab9a0cd1428a3e86bd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jun 2021 10:05:56 +0200 Subject: [PATCH 10/11] Bump thomaseizinger/create-pull-request from 1.0.0 to 1.1.0 (#401) * Bump thomaseizinger/create-pull-request from 1.0.0 to 1.1.0 Bumps [thomaseizinger/create-pull-request](https://github.com/thomaseizinger/create-pull-request) from 1.0.0 to 1.1.0. - [Release notes](https://github.com/thomaseizinger/create-pull-request/releases) - [Changelog](https://github.com/thomaseizinger/create-pull-request/blob/master/CHANGELOG.md) - [Commits](https://github.com/thomaseizinger/create-pull-request/compare/1.0.0...1.1.0) --- updated-dependencies: - dependency-name: thomaseizinger/create-pull-request dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Update CHANGELOG Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Damien Nguyen --- .github/workflows/draft_release.yml | 2 +- .github/workflows/publish_release.yml | 2 +- CHANGELOG.md | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/draft_release.yml b/.github/workflows/draft_release.yml index 3843cc5be..e83fc5c07 100644 --- a/.github/workflows/draft_release.yml +++ b/.github/workflows/draft_release.yml @@ -47,7 +47,7 @@ jobs: run: git flow release publish ${{ github.event.inputs.version }} - name: Create pull request - uses: thomaseizinger/create-pull-request@1.0.0 + uses: thomaseizinger/create-pull-request@1.1.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index abc0a7364..6a7989adc 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -131,7 +131,7 @@ jobs: needs: release # Only create PR if everything was successful steps: - name: Merge master into develop branch - uses: thomaseizinger/create-pull-request@1.0.0 + uses: thomaseizinger/create-pull-request@1.1.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fe324dce..c3962290e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Repository -- Updated cibuildwheels to 1.11.1 +- Updated cibuildwheels action to v1.11.1 +- Updated thomaseizinger/create-pull-request action to v1.1.0 ## [0.5.1] - 2019-02-15 From e0ff304f280c81330f31cf1e91d2ee2a900a9c11 Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Wed, 23 Jun 2021 12:36:47 +0000 Subject: [PATCH 11/11] Preparing release v0.6.0 - Fix some minor issues prior to releasing + Fixed pyproject.toml sections for pylint + Fixed constant warning messages if braket extra is not installed + Fixed file ignore list for check-manifest + Fixed issue with version in GitHub Actions + Added `setup_require` section to setup.cfg + Added new extra for testing (will eventually replace requirements_tests.txt) + Update pre-commit config to run black before linters + Update badges in README + Update configuration for pylint and pytest in pyproject.toml + Restructure and update CHANGELOG - Enable pylint in pre-commit hooks - Apply changes based on running pylint - Rename `m` parameter of the LocalOptimizer to `cache_size` - Gracious exit in case of failure in the MainEngine constructor - Remove requirements_tests.txt and improve gen_reqfile setup command - Remove docs/requirements.txt and add .readthedocs.yaml - Remove all assert statements in non-test code - Reinstate flaky test for phase estimation decomposition --- .github/workflows/ci.yml | 46 +-- .github/workflows/publish_release.yml | 27 ++ .pre-commit-config.yaml | 19 +- .readthedocs.yaml | 19 ++ CHANGELOG.md | 56 +++- MANIFEST.in | 1 - README.rst | 23 +- docs/conf.py | 5 +- docs/package_description.py | 28 +- docs/requirements.txt | 2 - docs/tutorials.rst | 8 +- examples/aqt.py | 2 + examples/bellpair_circuit.py | 2 + examples/control_tester.py | 2 + examples/gate_zoo.py | 2 + examples/grover.py | 2 + examples/hws4.py | 2 + examples/hws6.py | 2 + examples/ibm.py | 2 + examples/ionq.py | 2 + examples/ionq_bv.py | 2 + examples/ionq_half_adder.py | 2 + examples/quantum_random_numbers.py | 2 + examples/quantum_random_numbers_ibm.py | 2 + examples/shor.py | 2 + examples/teleport.py | 2 + examples/teleport_circuit.py | 2 + projectq/backends/_aqt/__init__.py | 2 + projectq/backends/_aqt/_aqt.py | 35 +- projectq/backends/_aqt/_aqt_http_client.py | 55 ++-- .../backends/_aqt/_aqt_http_client_test.py | 23 +- projectq/backends/_awsbraket/__init__.py | 21 +- projectq/backends/_awsbraket/_awsbraket.py | 152 ++++----- .../_awsbraket/_awsbraket_boto3_client.py | 84 ++--- .../_awsbraket_boto3_client_test.py | 8 +- projectq/backends/_circuits/__init__.py | 2 + projectq/backends/_circuits/_drawer.py | 22 +- .../backends/_circuits/_drawer_matplotlib.py | 28 +- .../_circuits/_drawer_matplotlib_test.py | 7 + projectq/backends/_circuits/_drawer_test.py | 8 +- projectq/backends/_circuits/_plot.py | 12 +- projectq/backends/_circuits/_to_latex.py | 229 +++++++------ projectq/backends/_circuits/_to_latex_test.py | 25 +- projectq/backends/_ibm/__init__.py | 2 + projectq/backends/_ibm/_ibm.py | 71 ++-- projectq/backends/_ibm/_ibm_http_client.py | 55 ++-- projectq/backends/_ibm/_ibm_test.py | 20 ++ projectq/backends/_ionq/__init__.py | 2 + projectq/backends/_ionq/_ionq.py | 13 +- projectq/backends/_ionq/_ionq_http_client.py | 29 +- .../backends/_ionq/_ionq_http_client_test.py | 6 +- projectq/backends/_ionq/_ionq_mapper.py | 2 + projectq/backends/_printer.py | 53 ++- projectq/backends/_printer_test.py | 10 + projectq/backends/_resource.py | 52 ++- projectq/backends/_sim/__init__.py | 2 + .../backends/_sim/_classical_simulator.py | 86 +++-- .../_sim/_classical_simulator_test.py | 9 + .../backends/_sim/_cppkernels/simulator.hpp | 3 +- projectq/backends/_sim/_pysim.py | 284 ++++++++-------- projectq/backends/_sim/_simulator.py | 39 ++- projectq/backends/_sim/_simulator_test.py | 9 + projectq/cengines/__init__.py | 2 + projectq/cengines/_basicmapper.py | 23 +- projectq/cengines/_basics.py | 81 ++--- projectq/cengines/_cmdmodifier.py | 23 +- projectq/cengines/_ibm5qubitmapper.py | 6 +- projectq/cengines/_linearmapper.py | 175 +++++----- projectq/cengines/_main.py | 134 ++++---- projectq/cengines/_main_test.py | 2 +- projectq/cengines/_manualmapper.py | 31 +- projectq/cengines/_optimize.py | 121 +++---- projectq/cengines/_optimize_test.py | 27 +- .../cengines/_replacer/_decomposition_rule.py | 6 +- .../_replacer/_decomposition_rule_set.py | 9 +- projectq/cengines/_replacer/_replacer.py | 58 ++-- projectq/cengines/_swapandcnotflipper.py | 13 +- projectq/cengines/_swapandcnotflipper_test.py | 16 +- projectq/cengines/_tagremover.py | 37 +-- projectq/cengines/_tagremover_test.py | 7 + projectq/cengines/_testengine.py | 56 ++-- projectq/cengines/_twodmapper.py | 179 +++++------ projectq/libs/__init__.py | 2 + projectq/libs/hist/_histogram.py | 10 +- projectq/libs/math/_constantmath.py | 50 +-- projectq/libs/math/_constantmath_test.py | 12 + projectq/libs/math/_default_rules.py | 28 +- projectq/libs/math/_gates.py | 83 +++-- projectq/libs/math/_quantummath.py | 303 +++++++++--------- projectq/libs/math/_quantummath_test.py | 122 ++++++- projectq/libs/revkit/__init__.py | 2 + projectq/libs/revkit/_control_function.py | 18 +- projectq/libs/revkit/_permutation.py | 11 +- projectq/libs/revkit/_phase.py | 19 +- projectq/libs/revkit/_utils.py | 11 +- projectq/meta/__init__.py | 4 +- projectq/meta/_compute.py | 120 ++++--- projectq/meta/_control.py | 46 +-- projectq/meta/_control_test.py | 10 +- projectq/meta/_dagger.py | 8 +- projectq/meta/_dirtyqubit.py | 2 +- projectq/meta/_logicalqubit.py | 2 +- projectq/meta/_loop.py | 17 +- projectq/meta/_util.py | 4 + projectq/ops/__init__.py | 2 + projectq/ops/_basics.py | 216 ++++++------- projectq/ops/_basics_test.py | 10 + projectq/ops/_command.py | 98 +++--- projectq/ops/_command_test.py | 3 + projectq/ops/_gates.py | 39 ++- projectq/ops/_metagates.py | 70 ++-- projectq/ops/_metagates_test.py | 12 + projectq/ops/_qaagate.py | 27 +- projectq/ops/_qftgate.py | 2 + projectq/ops/_qpegate.py | 2 + projectq/ops/_qubit_operator.py | 150 ++++----- projectq/ops/_shortcuts.py | 4 +- projectq/ops/_state_prep.py | 2 + projectq/ops/_time_evolution.py | 63 ++-- .../ops/_uniformly_controlled_rotation.py | 45 ++- projectq/setups/__init__.py | 2 + projectq/setups/_utils.py | 159 +++++++++ projectq/setups/aqt.py | 7 +- projectq/setups/awsbraket.py | 6 +- .../decompositions/amplitudeamplification.py | 12 +- .../decompositions/arb1qubit2rzandry.py | 32 +- projectq/setups/decompositions/barrier.py | 5 +- .../decompositions/carb1qubit2cnotrzandry.py | 96 +++--- projectq/setups/decompositions/cnot2rxx.py | 7 +- .../setups/decompositions/cnu2toffoliandcu.py | 14 +- projectq/setups/decompositions/crz2cxandrz.py | 10 +- projectq/setups/decompositions/entangle.py | 8 +- projectq/setups/decompositions/globalphase.py | 5 +- projectq/setups/decompositions/h2rx.py | 6 +- projectq/setups/decompositions/ph2r.py | 4 +- .../setups/decompositions/phaseestimation.py | 18 +- .../decompositions/phaseestimation_test.py | 10 +- .../decompositions/qft2crandhadamard.py | 2 +- .../setups/decompositions/qubitop2onequbit.py | 3 +- .../decompositions/qubitop2onequbit_test.py | 10 +- projectq/setups/decompositions/r2rzandph.py | 2 +- projectq/setups/decompositions/rx2rz.py | 2 +- projectq/setups/decompositions/ry2rz.py | 2 +- projectq/setups/decompositions/rz2rx.py | 6 +- .../setups/decompositions/sqrtswap2cnot.py | 6 +- .../decompositions/sqrtswap2cnot_test.py | 16 +- .../setups/decompositions/stateprep2cnot.py | 25 +- .../decompositions/stateprep2cnot_test.py | 8 + .../setups/decompositions/time_evolution.py | 32 +- .../decompositions/time_evolution_test.py | 34 +- .../decompositions/toffoli2cnotandtgate.py | 20 +- .../uniformlycontrolledr2cnot.py | 4 +- projectq/setups/default.py | 3 + projectq/setups/grid.py | 158 ++------- projectq/setups/ibm.py | 14 +- projectq/setups/ionq.py | 3 + projectq/setups/linear.py | 159 ++------- projectq/setups/restrictedgateset.py | 64 ++-- projectq/setups/trapped_ion_decomposer.py | 23 +- projectq/types/__init__.py | 2 + projectq/types/_qubit.py | 100 ++---- projectq/types/_qubit_test.py | 6 - pyproject.toml | 41 ++- requirements_tests.txt | 5 - setup.cfg | 17 + setup.py | 163 ++++++---- 166 files changed, 2978 insertions(+), 2714 deletions(-) create mode 100644 .readthedocs.yaml delete mode 100644 docs/requirements.txt create mode 100644 projectq/setups/_utils.py delete mode 100644 requirements_tests.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f95d55e5..58440eca0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,19 +50,32 @@ jobs: key: ${{ runner.os }}-${{ matrix.python }}-pip-${{ hashFiles('**/setup.cfg') }} restore-keys: ${{ runner.os }}-${{ matrix.python }}-pip- + - name: Generate requirement file (Unix) + if: runner.os != 'Windows' + run: | + python setup.py gen_reqfile --include-extras=test,braket,revkit + + - name: Generate requirement file (Windows) + if: runner.os == 'Windows' + run: | + python setup.py gen_reqfile --include-extras=test,braket + - name: Prepare env run: | - python setup.py gen_reqfile --include-extras python -m pip install -r requirements.txt --prefer-binary - python -m pip install -r requirements_tests.txt --prefer-binary python -m pip install coveralls - name: Setup annotations on Linux if: runner.os == 'Linux' run: python -m pip install pytest-github-actions-annotate-failures - - name: Build and install package - run: python -m pip install -ve .[braket] + - name: Build and install package (Unix) + if: runner.os != 'Windows' + run: python -m pip install -ve .[braket,revkit,test] + + - name: Build and install package (Windows) + if: runner.os == 'Windows' + run: python -m pip install -ve .[braket,test] - name: Pytest run: | @@ -125,15 +138,14 @@ jobs: - name: Prepare Python env run: | - python3 setup.py gen_reqfile --include-extras + python3 setup.py gen_reqfile --include-extras=test,braket python3 -m pip install -r requirements.txt --prefer-binary - python3 -m pip install -r requirements_tests.txt --prefer-binary - name: Upgrade pybind11 and flaky run: python3 -m pip install --upgrade pybind11 flaky --prefer-binary - name: Build and install package - run: python3 -m pip install -ve .[braket] + run: python3 -m pip install -ve .[braket,test] - name: Pytest run: | @@ -170,15 +182,14 @@ jobs: - name: Prepare Python env run: | - python3 setup.py gen_reqfile --include-extras + python3 setup.py gen_reqfile --include-extras=test,braket python3 -m pip install -r requirements.txt --prefer-binary - python3 -m pip install -r requirements_tests.txt --prefer-binary - name: Upgrade pybind11 and flaky run: python3 -m pip install --upgrade pybind11 flaky --prefer-binary - name: Build and install package - run: python3 -m pip install -ve .[braket] + run: python3 -m pip install -ve .[braket,test] - name: Pytest run: | @@ -244,12 +255,11 @@ jobs: - name: Install dependencies run: | - python3 setup.py gen_reqfile --include-extras + python3 setup.py gen_reqfile --include-extras=test,braket python3 -m pip install -r requirements.txt --prefer-binary - python3 -m pip install -r requirements_tests.txt --prefer-binary - name: Build and install package - run: python3 -m pip install -ve .[braket] + run: python3 -m pip install -ve .[braket,test] - name: Pytest run: | @@ -283,10 +293,7 @@ jobs: - name: Install docs & setup requirements run: | - python3 setup.py gen_reqfile --include-extras - python3 -m pip install -r requirements.txt --prefer-binary - python3 -m pip install -r docs/requirements.txt --prefer-binary - python3 -m pip install . + python3 -m pip install .[docs] - name: Build docs run: python3 -m sphinx -b html docs docs/.build @@ -334,12 +341,11 @@ jobs: - name: Prepare env run: | - python setup.py gen_reqfile --include-extras + python setup.py gen_reqfile --include-extras=test,braket python -m pip install -r requirements.txt --prefer-binary - python -m pip install -r requirements_tests.txt --prefer-binary - name: Build and install package - run: python -m pip install -ve .[braket] + run: python -m pip install -ve .[braket,test] - name: Run all checks run: | diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 6a7989adc..8717a479a 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -27,6 +27,33 @@ jobs: git fetch --prune --unshallow git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - name: Extract version from tag name + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + run: | + TAG_NAME="${GITHUB_REF/refs\/tags\//}" + VERSION=${TAG_NAME#v} + + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + + - name: Extract version from branch name (for release branches) + if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/') + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + VERSION=${BRANCH_NAME#release/} + + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + + - name: Extract version from branch name (for hotfix branches) + if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/') + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + VERSION=${BRANCH_NAME#hotfix/} + + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + + - name: Set tag for setuptools-scm + run: git tag v${RELEASE_VERSION} master + - name: Build wheels uses: joerick/cibuildwheel@v1.11.1 env: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 523c4e98e..03afd8fe9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,9 @@ # # See https://github.com/pre-commit/pre-commit +ci: + skip: [check-manifest] + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 @@ -25,7 +28,6 @@ repos: - id: debug-statements - id: end-of-file-fixer - id: mixed-line-ending - - id: requirements-txt-fixer - id: trailing-whitespace - id: fix-encoding-pragma @@ -35,12 +37,6 @@ repos: hooks: - id: remove-tabs -- repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 - hooks: - - id: flake8 - exclude: ^(docs/.*|tools/.*)$ - - repo: https://github.com/psf/black rev: 21.5b1 hooks: @@ -49,13 +45,20 @@ repos: # This is a slow hook, so only run this if --hook-stage manual is passed stages: [manual] +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + exclude: ^(docs/.*|tools/.*)$ + - repo: https://github.com/pre-commit/mirrors-pylint rev: 'v3.0.0a3' hooks: - id: pylint - args: ['--score=n', '--exit-zero'] + args: ['--score=n'] # This is a slow hook, so only run this if --hook-stage manual is passed stages: [manual] + additional_dependencies: ['pybind11>=2.6', 'numpy', 'requests', 'boto3', 'matplotlib', 'networkx'] - repo: https://github.com/mgedmin/check-manifest rev: "0.46" diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..9199f30a8 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,19 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +sphinx: + configuration: docs/conf.py + +formats: all + +python: + version: 3.8 + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/CHANGELOG.md b/CHANGELOG.md index c3962290e..46af704bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,31 +8,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +### Changed +### Deprecated +### Fixed +### Removed +### Repository -- Support for GitHub Actions - * Build and testing on various plaforms and compilers - * Automatic draft of new release - * Automatic publication of new release once ready - * Automatic upload of releases artifacts to PyPi and GitHub -- Use ``setuptools-scm`` for versioning -- Added ``.editorconfig` file -- Added ``pyproject.toml`` and ``setup.cfg`` -- Added CHANGELOG.md -- Added backend for IonQ. -- Added support for state-dependent qubit control +## [0.6.0] - 2021-06-23 + +### Added + +- New backend for the IonQ platform +- New backend for the AWS Braket platform +- New gates for quantum math operations on quantum registers +- Support for state-dependent control qubits (ie. negatively or positively controlled gates) + +### Changed + +- Name of the single parameter of the `LocalOptimizer` has been changed from `m` to `cache_size` in order to better represent its actual use. ### Deprecated -- Compatibility with Python <= 3.5 +- Compatibility with Python <= 3.5 +- `LocalOptimizer(m=10)` should be changed into `LocalOptimizer(cache_size=10)`. Using of the old name is still possible, but is deprecated and will be removed in a future version of ProjectQ. + +### Fixed + +- Installation on Mac OS Big Sur +- IBM Backend issues with new API ### Removed - Compatibility with Python 2.7 +- Support for multi-qubit measurement gates has been dropped; use `All(Measure) | qureg` instead ### Repository -- Updated cibuildwheels action to v1.11.1 -- Updated thomaseizinger/create-pull-request action to v1.1.0 +- Use `setuptools-scm` for versioning +- Added `.editorconfig` file +- Added `pyproject.toml` and `setup.cfg` +- Added CHANGELOG.md +- Added support for GitHub Actions + - Build and testing on various plaforms and compilers + - Automatic draft of new release + - Automatic publication of new release once ready + - Automatic upload of releases artifacts to PyPi and GitHub +- Added pre-commit configuration file + +- Updated cibuildwheels action to v1.11.1 +- Updated thomaseizinger/create-pull-request action to v1.1.0 ## [0.5.1] - 2019-02-15 @@ -71,3 +95,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The ProjectQ v0.5.x release branch is the last one that is guaranteed to work with Python 2.7.x. Future releases might introduce changes that will require Python 3.5 (Python 3.4 and earlier have already been declared deprecated at the time of this writing) + +[Unreleased]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.6.0...HEAD + +[0.6.0]: https://github.com/ProjectQ-Framework/ProjectQ/compare/0.5.1...0.6.0 diff --git a/MANIFEST.in b/MANIFEST.in index aa5655eab..30dbe21d7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,7 +3,6 @@ include CHANGELOG.md include MANIFEST.in include NOTICE include README.rst -include requirements*.txt include setup.py include setup.cfg include pyproject.toml diff --git a/README.rst b/README.rst index c93c2165b..3e24d7332 100755 --- a/README.rst +++ b/README.rst @@ -1,20 +1,23 @@ ProjectQ - An open source software framework for quantum computing ================================================================== -.. image:: https://travis-ci.org/ProjectQ-Framework/ProjectQ.svg?branch=master - :target: https://travis-ci.org/ProjectQ-Framework/ProjectQ +.. image:: https://img.shields.io/pypi/pyversions/projectq?label=Python + :alt: PyPI - Python Version -.. image:: https://coveralls.io/repos/github/ProjectQ-Framework/ProjectQ/badge.svg - :target: https://coveralls.io/github/ProjectQ-Framework/ProjectQ +.. image:: https://badge.fury.io/py/projectq.svg + :target: https://badge.fury.io/py/projectq -.. image:: https://readthedocs.org/projects/projectq/badge/?version=latest - :target: http://projectq.readthedocs.io/en/latest/?badge=latest - :alt: Documentation Status +.. image:: https://github.com/ProjectQ-Framework/ProjectQ/actions/workflows/ci.yml/badge.svg + :alt: CI Status + :target: https://github.com/ProjectQ-Framework/ProjectQ/actions/workflows/ci.yml -.. image:: https://badge.fury.io/py/projectq.svg - :target: https://badge.fury.io/py/projectq +.. image:: https://coveralls.io/repos/github/ProjectQ-Framework/ProjectQ/badge.svg + :alt: Coverage Status + :target: https://coveralls.io/github/ProjectQ-Framework/ProjectQ -.. image:: https://img.shields.io/badge/python-2.7%2C%203.4%2C%203.5%2C%203.6-brightgreen.svg +.. image:: https://readthedocs.org/projects/projectq/badge/?version=latest + :target: http://projectq.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status ProjectQ is an open source effort for quantum computing. diff --git a/docs/conf.py b/docs/conf.py index 5097d7854..46527b4fa 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - #!/usr/bin/env python3 # -*- coding: utf-8 -*- # @@ -18,7 +16,8 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# +# pylint: skip-file + import os import sys diff --git a/docs/package_description.py b/docs/package_description.py index a782a1aa8..6beeff848 100644 --- a/docs/package_description.py +++ b/docs/package_description.py @@ -1,13 +1,31 @@ # -*- coding: utf-8 -*- +# 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 containing some helper classes for generating the documentation""" + import inspect import sys import os -class PackageDescription(object): +class PackageDescription: # pylint: disable=too-many-instance-attributes,too-few-public-methods + """Class representing a package description""" + package_list = [] - def __init__( + def __init__( # pylint: disable=too-many-arguments self, pkg_name, desc='', @@ -80,7 +98,10 @@ def __init__( ] self.members.sort(key=lambda x: x[0].lower()) - def get_ReST(self): + def get_ReST(self): # pylint: disable=invalid-name,too-many-branches,too-many-statements + """ + Conversion to ReST formatted string. + """ new_lines = [] new_lines.append(self.name) new_lines.append('=' * len(self.name)) @@ -164,5 +185,4 @@ def get_ReST(self): new_lines.append(' {}'.format(param)) new_lines.append('') - assert not new_lines[-1] return new_lines[:-1] diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 82133027c..000000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -sphinx -sphinx_rtd_theme diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 13cb08624..4df54c9fb 100755 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -15,7 +15,9 @@ To start using ProjectQ, simply run python -m pip install --user projectq -or, alternatively, `clone/download `_ this repo (e.g., to your /home directory) and run +Since version 0.6.0, ProjectQ is available as pre-compiled binary wheels in addition to the traditional source package. These wheels should work on most platforms, provided that your processor supports AVX2 instructions. Should you encounter any troubles while installation ProjectQ in binary form, you can always try tom compile the project manually as described below. You may want to pass the `--no-binary projectq` flag to Pip during the installation to make sure that you are downloading the source package. + +Alternatively, you can also `clone/download `_ this repository (e.g., to your /home directory) and run .. code-block:: bash @@ -34,9 +36,9 @@ ProjectQ comes with a high-performance quantum simulator written in C++. Please .. code-block:: bash - env CC=g++-5 python -m pip install --user projectq + env CC=g++-10 python -m pip install --user projectq - Please note that the compiler you specify must support at leaste **C++11**! + Please note that the compiler you specify must support at least **C++11**! .. note:: Please use pip version v6.1.0 or higher as this ensures that dependencies are installed in the `correct order `_. diff --git a/examples/aqt.py b/examples/aqt.py index e2fb6af12..e5fb6f658 100644 --- a/examples/aqt.py +++ b/examples/aqt.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + import matplotlib.pyplot as plt import getpass diff --git a/examples/bellpair_circuit.py b/examples/bellpair_circuit.py index fff144602..d652ebc33 100755 --- a/examples/bellpair_circuit.py +++ b/examples/bellpair_circuit.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + import matplotlib.pyplot as plt from projectq import MainEngine diff --git a/examples/control_tester.py b/examples/control_tester.py index 94833a70e..5e671fafe 100755 --- a/examples/control_tester.py +++ b/examples/control_tester.py @@ -12,6 +12,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# pylint: skip-file + from projectq.cengines import MainEngine from projectq.meta import Control diff --git a/examples/gate_zoo.py b/examples/gate_zoo.py index 6abe51c00..404d9822b 100644 --- a/examples/gate_zoo.py +++ b/examples/gate_zoo.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + import os import sys diff --git a/examples/grover.py b/examples/grover.py index d845d148a..feff9ef58 100755 --- a/examples/grover.py +++ b/examples/grover.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + import math from projectq import MainEngine diff --git a/examples/hws4.py b/examples/hws4.py index 3b0ab9a0b..14a4fe551 100644 --- a/examples/hws4.py +++ b/examples/hws4.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + from projectq.cengines import MainEngine from projectq.ops import All, H, X, Measure from projectq.meta import Compute, Uncompute diff --git a/examples/hws6.py b/examples/hws6.py index 6a17b532f..38892f371 100644 --- a/examples/hws6.py +++ b/examples/hws6.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + from projectq.cengines import MainEngine from projectq.ops import All, H, X, Measure from projectq.meta import Compute, Uncompute, Dagger diff --git a/examples/ibm.py b/examples/ibm.py index 6bc2913e9..6914d051f 100755 --- a/examples/ibm.py +++ b/examples/ibm.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + import matplotlib.pyplot as plt import getpass diff --git a/examples/ionq.py b/examples/ionq.py index 1eaf2d136..8ca8cc66b 100644 --- a/examples/ionq.py +++ b/examples/ionq.py @@ -12,6 +12,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# pylint: skip-file + """Example of a basic entangling operation using an IonQBackend.""" import getpass diff --git a/examples/ionq_bv.py b/examples/ionq_bv.py index a1135db8c..61aa3c16e 100644 --- a/examples/ionq_bv.py +++ b/examples/ionq_bv.py @@ -12,6 +12,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# pylint: skip-file + """Example of a basic Bernstein-Vazirani circuit using an IonQBackend.""" import getpass diff --git a/examples/ionq_half_adder.py b/examples/ionq_half_adder.py index 33fb995e0..798ed4ac4 100644 --- a/examples/ionq_half_adder.py +++ b/examples/ionq_half_adder.py @@ -12,6 +12,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# pylint: skip-file + """Example of a basic 'half-adder' circuit using an IonQBackend""" import getpass diff --git a/examples/quantum_random_numbers.py b/examples/quantum_random_numbers.py index 76d51e178..796603d37 100755 --- a/examples/quantum_random_numbers.py +++ b/examples/quantum_random_numbers.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + from projectq.ops import H, Measure from projectq import MainEngine diff --git a/examples/quantum_random_numbers_ibm.py b/examples/quantum_random_numbers_ibm.py index 15bf9b80c..77e427434 100755 --- a/examples/quantum_random_numbers_ibm.py +++ b/examples/quantum_random_numbers_ibm.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + import projectq.setups.ibm from projectq.ops import H, Measure from projectq import MainEngine diff --git a/examples/shor.py b/examples/shor.py index c3066e780..f604abb25 100755 --- a/examples/shor.py +++ b/examples/shor.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + from __future__ import print_function import math diff --git a/examples/teleport.py b/examples/teleport.py index 499767868..2a6b964da 100755 --- a/examples/teleport.py +++ b/examples/teleport.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + from projectq.ops import CNOT, H, Measure, Rz, X, Z from projectq import MainEngine from projectq.meta import Dagger, Control diff --git a/examples/teleport_circuit.py b/examples/teleport_circuit.py index bf1ff5e0d..1f002b915 100755 --- a/examples/teleport_circuit.py +++ b/examples/teleport_circuit.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +# pylint: skip-file + from projectq import MainEngine from projectq.backends import CircuitDrawer diff --git a/projectq/backends/_aqt/__init__.py b/projectq/backends/_aqt/__init__.py index 08893cf17..7c5dcb45e 100644 --- a/projectq/backends/_aqt/__init__.py +++ b/projectq/backends/_aqt/__init__.py @@ -13,4 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ProjectQ module for supporting the AQT platform""" + from ._aqt import AQTBackend diff --git a/projectq/backends/_aqt/_aqt.py b/projectq/backends/_aqt/_aqt.py index df5a41a8c..e250e239c 100644 --- a/projectq/backends/_aqt/_aqt.py +++ b/projectq/backends/_aqt/_aqt.py @@ -20,6 +20,7 @@ from projectq.cengines import BasicEngine from projectq.meta import get_control_count, LogicalQubitIDTag from projectq.ops import Rx, Ry, Rxx, Measure, Allocate, Barrier, Deallocate, FlushGate +from projectq.types import WeakQubitRef from ._aqt_http_client import send, retrieve @@ -38,11 +39,10 @@ def _format_counts(samples, length): counts[h_result] = 1 else: counts[h_result] += 1 - counts = {k: v for k, v in sorted(counts.items(), key=lambda item: item[0])} - return counts + return dict(sorted(counts.items(), key=lambda item: item[0])) -class AQTBackend(BasicEngine): +class AQTBackend(BasicEngine): # pylint: disable=too-many-instance-attributes """ The AQT Backend class, which stores the circuit, transforms it to the appropriate data format, and sends the circuit through the AQT API. @@ -58,7 +58,7 @@ def __init__( num_retries=3000, interval=1, retrieve_execution=None, - ): + ): # pylint: disable=too-many-arguments """ Initialize the Backend object. @@ -142,14 +142,12 @@ def _store(self, cmd): if gate == Deallocate: return if gate == Measure: - assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 qb_id = cmd.qubits[0][0].id logical_id = None for tag in cmd.tags: if isinstance(tag, LogicalQubitIDTag): logical_id = tag.logical_qubit_id break - # assert logical_id is not None if logical_id is None: logical_id = qb_id self._mapper.append(qb_id) @@ -190,13 +188,13 @@ def _logical_to_physical(self, qb_id): "was eliminated during optimization.".format(qb_id) ) return mapping[qb_id] - except AttributeError: + except AttributeError as err: if qb_id not in self._mapper: raise RuntimeError( "Unknown qubit id {}. Please make sure " "eng.flush() was called and that the qubit " "was eliminated during optimization.".format(qb_id) - ) + ) from err return qb_id def get_probabilities(self, qureg): @@ -264,7 +262,6 @@ def _run(self): info, device=self.device, token=self._token, - shots=self._num_runs, num_retries=self._num_retries, interval=self._interval, verbose=self._verbose, @@ -281,37 +278,33 @@ def _run(self): self._num_runs = len(res) counts = _format_counts(res, n_qubit) # Determine random outcome - P = random.random() + random_outcome = random.random() p_sum = 0.0 measured = "" for state in counts: probability = counts[state] * 1.0 / self._num_runs p_sum += probability star = "" - if p_sum >= P and measured == "": + if p_sum >= random_outcome and measured == "": measured = state star = "*" self._probabilities[state] = probability if self._verbose and probability > 0: print(str(state) + " with p = " + str(probability) + star) - class QB: - def __init__(self, qubit_id): - self.id = qubit_id - - # register measurement result + # register measurement result from AQT for qubit_id in self._measured_ids: location = self._logical_to_physical(qubit_id) result = int(measured[location]) - self.main_engine.set_measurement_result(QB(qubit_id), result) + self.main_engine.set_measurement_result(WeakQubitRef(self, qubit_id), result) self._reset() - except TypeError: - raise Exception("Failed to run the circuit. Aborting.") + except TypeError as err: + raise Exception("Failed to run the circuit. Aborting.") from err def receive(self, command_list): """ - Receives a command list and, for each command, stores it until - completion. + Receives a command list and, for each command, stores it until completion. Upon flush, send the data to the + AQT API. Args: command_list: List of commands to execute diff --git a/projectq/backends/_aqt/_aqt_http_client.py b/projectq/backends/_aqt/_aqt_http_client.py index 05fa0220a..a38b69329 100644 --- a/projectq/backends/_aqt/_aqt_http_client.py +++ b/projectq/backends/_aqt/_aqt_http_client.py @@ -29,8 +29,10 @@ class AQT(Session): + """Class managing the session to AQT's APIs""" + def __init__(self): - super(AQT, self).__init__() + super().__init__() self.backends = dict() self.timeout = 5.0 self.token = None @@ -57,7 +59,15 @@ def update_devices_list(self, verbose=False): print(self.backends) def is_online(self, device): - # useless at the moment, may change if API evolves + """ + Check whether a device is currently online + + Args: + device (str): name of the aqt device to use + + Note: + Useless at the moment, may change if the API evolves + """ return device in self.backends def can_run_experiment(self, info, device): @@ -75,7 +85,7 @@ def can_run_experiment(self, info, device): 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): AQT user API token. @@ -85,14 +95,15 @@ def _authenticate(self, token=None): self.headers.update({'Ocp-Apim-Subscription-Key': token, 'SDK': 'ProjectQ'}) self.token = token - def _run(self, info, device): + def run(self, info, device): + """Run a quantum circuit""" argument = { 'data': info['circuit'], 'access_token': self.token, 'repetitions': info['shots'], 'no_qubits': info['nq'], } - req = super(AQT, self).put(urljoin(_API_URL, self.backends[device]['url']), data=argument) + req = super().put(urljoin(_API_URL, self.backends[device]['url']), data=argument) req.raise_for_status() r_json = req.json() if r_json['status'] != 'queued': @@ -100,8 +111,12 @@ def _run(self, info, device): execution_id = r_json["id"] return execution_id - def _get_result(self, device, execution_id, num_retries=3000, interval=1, verbose=False): - + def get_result( # pylint: disable=too-many-arguments + self, device, execution_id, num_retries=3000, interval=1, verbose=False + ): + """ + Get the result of an execution + """ if verbose: print("Waiting for results. [Job ID: {}]".format(execution_id)) @@ -116,7 +131,7 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover for retries in range(num_retries): argument = {'id': execution_id, 'access_token': self.token} - req = super(AQT, self).put(urljoin(_API_URL, self.backends[device]['url']), data=argument) + req = super().put(urljoin(_API_URL, self.backends[device]['url']), data=argument) req.raise_for_status() r_json = req.json() if r_json['status'] == 'finished' or 'samples' in r_json: @@ -131,7 +146,7 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover # available if not self.is_online(device): # pragma: no cover raise DeviceOfflineError( - "Device went offline. The ID of " "your submitted job is {}.".format(execution_id) + "Device went offline. The ID of your submitted job is {}.".format(execution_id) ) finally: @@ -142,11 +157,11 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover class DeviceTooSmall(Exception): - pass + """Exception raised if the device is too small to run the circuit""" class DeviceOfflineError(Exception): - pass + """Exception raised if a selected device is currently offline""" def show_devices(verbose=False): @@ -165,7 +180,7 @@ def show_devices(verbose=False): return aqt_session.backends -def retrieve(device, token, jobid, num_retries=3000, interval=1, verbose=False): +def retrieve(device, token, jobid, num_retries=3000, interval=1, verbose=False): # pylint: disable=too-many-arguments """ Retrieves a previously run job by its ID. @@ -178,9 +193,9 @@ def retrieve(device, token, jobid, num_retries=3000, interval=1, verbose=False): (list) samples form the AQT server """ aqt_session = AQT() - aqt_session._authenticate(token) + aqt_session.authenticate(token) aqt_session.update_devices_list(verbose) - res = aqt_session._get_result(device, jobid, num_retries=num_retries, interval=interval, verbose=verbose) + res = aqt_session.get_result(device, jobid, num_retries=num_retries, interval=interval, verbose=verbose) return res @@ -188,11 +203,10 @@ def send( info, device='aqt_simulator', token=None, - shots=100, num_retries=100, interval=1, verbose=False, -): +): # pylint: disable=too-many-arguments """ Sends cicruit through the AQT API and runs the quantum circuit. @@ -200,8 +214,6 @@ def send( info(dict): Contains representation of the circuit to run. device (str): name of the aqt device. Simulator chosen by default token (str): AQT user API token. - shots (int): Number of runs of the same circuit to collect - statistics. max for AQT is 200. 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). @@ -217,7 +229,7 @@ def send( print("- Authenticating...") if token is not None: print('user API token: ' + token) - aqt_session._authenticate(token) + aqt_session.authenticate(token) # check if the device is online aqt_session.update_devices_list(verbose) @@ -238,10 +250,10 @@ def send( raise DeviceTooSmall("Device is too small.") if verbose: print("- Running code: {}".format(info)) - execution_id = aqt_session._run(info, device) + execution_id = aqt_session.run(info, device) if verbose: print("- Waiting for results...") - res = aqt_session._get_result( + res = aqt_session.get_result( device, execution_id, num_retries=num_retries, @@ -260,3 +272,4 @@ def send( except KeyError as err: print("- Failed to parse response:") print(err) + return None diff --git a/projectq/backends/_aqt/_aqt_http_client_test.py b/projectq/backends/_aqt/_aqt_http_client_test.py index 401c35e5c..64e50fee2 100644 --- a/projectq/backends/_aqt/_aqt_http_client_test.py +++ b/projectq/backends/_aqt/_aqt_http_client_test.py @@ -34,7 +34,7 @@ def test_is_online(): token = 'access' aqt_session = _aqt_http_client.AQT() - aqt_session._authenticate(token) + aqt_session.authenticate(token) aqt_session.update_devices_list() assert aqt_session.is_online('aqt_simulator') assert aqt_session.is_online('aqt_simulator_noise') @@ -48,7 +48,7 @@ def test_show_devices(): assert len(device_list) == 3 -def test_send_too_many_qubits(monkeypatch): +def test_send_too_many_qubits(): info = { 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' @@ -58,11 +58,10 @@ def test_send_too_many_qubits(monkeypatch): 'backend': {'name': 'aqt_simulator'}, } token = "access" - shots = 1 # Code to test: with pytest.raises(_aqt_http_client.DeviceTooSmall): - _aqt_http_client.send(info, device="aqt_simulator", token=token, shots=shots, verbose=True) + _aqt_http_client.send(info, device="aqt_simulator", token=token, verbose=True) def test_send_real_device_online_verbose(monkeypatch): @@ -83,7 +82,6 @@ def test_send_real_device_online_verbose(monkeypatch): 'backend': {'name': 'aqt_simulator'}, } token = "access" - shots = 1 execution_id = '3' result_ready = [False] result = "my_result" @@ -139,7 +137,7 @@ def user_password_input(prompt): monkeypatch.setattr("getpass.getpass", user_password_input) # Code to test: - res = _aqt_http_client.send(info, device="aqt_simulator", token=None, shots=shots, verbose=True) + res = _aqt_http_client.send(info, device="aqt_simulator", token=None, verbose=True) assert res == result @@ -157,7 +155,6 @@ def user_password_input(prompt): return token monkeypatch.setattr("getpass.getpass", user_password_input) - shots = 1 info = { 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' @@ -166,7 +163,7 @@ def user_password_input(prompt): 'shots': 1, 'backend': {'name': 'aqt_simulator'}, } - _aqt_http_client.send(info, device="aqt_simulator", token=None, shots=shots, verbose=True) + _aqt_http_client.send(info, device="aqt_simulator", token=None, verbose=True) def test_send_that_errors_are_caught2(monkeypatch): @@ -183,7 +180,6 @@ def user_password_input(prompt): return token monkeypatch.setattr("getpass.getpass", user_password_input) - shots = 1 info = { 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' @@ -192,7 +188,7 @@ def user_password_input(prompt): 'shots': 1, 'backend': {'name': 'aqt_simulator'}, } - _aqt_http_client.send(info, device="aqt_simulator", token=None, shots=shots, verbose=True) + _aqt_http_client.send(info, device="aqt_simulator", token=None, verbose=True) def test_send_that_errors_are_caught3(monkeypatch): @@ -209,7 +205,6 @@ def user_password_input(prompt): return token monkeypatch.setattr("getpass.getpass", user_password_input) - shots = 1 info = { 'circuit': '[["Y", 0.5, [1]], ["X", 0.5, [1]], ["X", 0.5, [1]], ' '["Y", 0.5, [1]], ["MS", 0.5, [1, 2]], ["X", 3.5, [1]], ' @@ -218,7 +213,7 @@ def user_password_input(prompt): 'shots': 1, 'backend': {'name': 'aqt_simulator'}, } - _aqt_http_client.send(info, device="aqt_simulator", token=None, shots=shots, verbose=True) + _aqt_http_client.send(info, device="aqt_simulator", token=None, verbose=True) def test_send_that_errors_are_caught4(monkeypatch): @@ -230,7 +225,6 @@ def test_send_that_errors_are_caught4(monkeypatch): } info = {'circuit': '[]', 'nq': 3, 'shots': 1, 'backend': {'name': 'aqt_simulator'}} token = "access" - shots = 1 execution_id = '123e' def mocked_requests_put(*args, **kwargs): @@ -265,7 +259,6 @@ def raise_for_status(self): device="aqt_simulator", token=token, num_retries=10, - shots=shots, verbose=True, ) @@ -288,7 +281,6 @@ def test_timeout_exception(monkeypatch): 'backend': {'name': 'aqt_simulator'}, } token = "access" - shots = 1 execution_id = '123e' tries = [0] @@ -338,7 +330,6 @@ def user_password_input(prompt): device="aqt_simulator", token=tok, num_retries=10, - shots=shots, verbose=True, ) assert "123e" in str(excinfo.value) # check that job id is in exception diff --git a/projectq/backends/_awsbraket/__init__.py b/projectq/backends/_awsbraket/__init__.py index db8d7a6ff..2d01597e0 100644 --- a/projectq/backends/_awsbraket/__init__.py +++ b/projectq/backends/_awsbraket/__init__.py @@ -13,18 +13,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ProjectQ module for supporting the AWS Braket platform""" + try: from ._awsbraket import AWSBraketBackend except ImportError: # pragma: no cover - import warnings - warnings.warn( - "Failed to import one of the dependencies required to use " - "the Amazon Braket Backend.\n" - "Did you install ProjectQ using the [braket] extra? " - "(python3 -m pip install projectq[braket])" - ) + class AWSBraketBackend: # pylint: disable=too-few-public-methods + """Dummy class""" - # Make sure that the symbol is defined - class AWSBraketBackend: - pass + def __init__(self, *args, **kwargs): + raise RuntimeError( + "Failed to import one of the dependencies required to use " + "the Amazon Braket Backend.\n" + "Did you install ProjectQ using the [braket] extra? " + "(python3 -m pip install projectq[braket])" + ) diff --git a/projectq/backends/_awsbraket/_awsbraket.py b/projectq/backends/_awsbraket/_awsbraket.py index 7f158afa1..9580c36f0 100755 --- a/projectq/backends/_awsbraket/_awsbraket.py +++ b/projectq/backends/_awsbraket/_awsbraket.py @@ -48,11 +48,10 @@ from ._awsbraket_boto3_client import send, retrieve -class AWSBraketBackend(BasicEngine): +class AWSBraketBackend(BasicEngine): # pylint: disable=too-many-instance-attributes """ - The AWS Braket Backend class, which stores the circuit, - transforms it to Braket compatible, - and sends the circuit through the Boto3 and Amazon Braket SDK. + The AWS Braket Backend class, which stores the circuit, transforms it to Braket compatible, and sends the circuit + through the Boto3 and Amazon Braket SDK. """ def __init__( @@ -66,30 +65,24 @@ def __init__( num_retries=30, interval=1, retrieve_execution=None, - ): + ): # pylint: disable=too-many-arguments """ Initialize the Backend object. Args: - use_hardware (bool): If True, the code is run on one of the AWS - Braket backends, by default on the Rigetti Aspen-8 chip - (instead of using the AWS Braket SV1 Simulator) - num_runs (int): Number of runs to collect statistics. - (default is 1000) - verbose (bool): If True, statistics are printed, in addition to the - measurement result being registered (at the end of the - circuit). - credentials (dict): mapping the AWS key credentials as the - AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. - device (str): name of the device to use. Rigetti Aspen-8 by - default. Valid names are "Aspen-8", "IonQ Device" and "SV1" - num_retries (int): Number of times to retry to obtain results from - AWS Braket. (default is 30) - interval (float, int): Number of seconds between successive - attempts to obtain results from AWS Braket. (default is 1) - retrieve_execution (str): TaskArn to retrieve instead of re-running - the circuit (e.g., if previous run timed out). The TaskArns - have the form: + use_hardware (bool): If True, the code is run on one of the AWS Braket backends, by default on the Rigetti + Aspen-8 chip (instead of using the AWS Braket SV1 Simulator) + num_runs (int): Number of runs to collect statistics. (default is 1000) + verbose (bool): If True, statistics are printed, in addition to the measurement result being registered + (at the end of the circuit). + credentials (dict): mapping the AWS key credentials as the AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. + device (str): name of the device to use. Rigetti Aspen-8 by default. Valid names are "Aspen-8", "IonQ + Device" and "SV1" + num_retries (int): Number of times to retry to obtain results from AWS Braket. (default is 30) + interval (float, int): Number of seconds between successive attempts to obtain results from AWS Braket. + (default is 1) + retrieve_execution (str): TaskArn to retrieve instead of re-running the circuit (e.g., if previous run + timed out). The TaskArns have the form: "arn:aws:braket:us-east-1:123456789012:quantum-task/5766032b-2b47-4bf9-cg00-f11851g4015b" """ BasicEngine.__init__(self) @@ -136,37 +129,29 @@ def __init__( self._circuittail = ']}' - def is_available(self, cmd): + def is_available(self, cmd): # pylint: disable=too-many-return-statements,too-many-branches """ Return true if the command can be executed. Depending on the device chosen, the operations available differ. The operations avialable for the Aspen-8 Rigetti device are: - - "cz" = Control Z, "xy" = Not available in ProjectQ, - "ccnot" = Toffoli (ie. controlled CNOT), "cnot" = Control X, - "cphaseshift" = Control R, - "cphaseshift00" "cphaseshift01" "cphaseshift10" = Not available + - "cz" = Control Z, "xy" = Not available in ProjectQ, "ccnot" = Toffoli (ie. controlled CNOT), "cnot" = + Control X, "cphaseshift" = Control R, "cphaseshift00" "cphaseshift01" "cphaseshift10" = Not available in ProjectQ, - "cswap" = Control Swap, "h" = H, "i" = Identity, not in ProjectQ, - "iswap" = Not available in ProjectQ, "phaseshift" = R, - "pswap" = Not available in ProjectQ, "rx" = Rx, "ry" = Ry, "rz" = Rz, - "s" = S, "si" = Sdag, "swap" = Swap, "t" = T, "ti" = Tdag, - "x" = X, "y" = Y, "z" = Z + "cswap" = Control Swap, "h" = H, "i" = Identity, not in ProjectQ, "iswap" = Not available in ProjectQ, + "phaseshift" = R, "pswap" = Not available in ProjectQ, "rx" = Rx, "ry" = Ry, "rz" = Rz, "s" = S, "si" = + Sdag, "swap" = Swap, "t" = T, "ti" = Tdag, "x" = X, "y" = Y, "z" = Z The operations available for the IonQ Device are: - - "x" = X, "y" = Y, "z" = Z, "rx" = Rx, "ry" = Ry, "rz" = Rz, "h", H, - "cnot" = Control X, "s" = S, "si" = Sdag, "t" = T, "ti" = Tdag, - "v" = SqrtX, "vi" = Not available in ProjectQ, - "xx" "yy" "zz" = Not available in ProjectQ, "swap" = Swap, - "i" = Identity, not in ProjectQ - - The operations available for the StateVector simulator (SV1) are - the union of the ones for Rigetti Aspen-8 and IonQ Device plus some - more: - - "cy" = Control Y, "unitary" = Arbitrary unitary gate defined as a - matrix equivalent to the MatrixGate in ProjectQ, "xy" = Not available - in ProjectQ + - "x" = X, "y" = Y, "z" = Z, "rx" = Rx, "ry" = Ry, "rz" = Rz, "h", H, "cnot" = Control X, "s" = S, "si" = + Sdag, "t" = T, "ti" = Tdag, "v" = SqrtX, "vi" = Not available in ProjectQ, "xx" "yy" "zz" = Not available in + ProjectQ, "swap" = Swap, "i" = Identity, not in ProjectQ + + The operations available for the StateVector simulator (SV1) are the union of the ones for Rigetti Aspen-8 and + IonQ Device plus some more: + - "cy" = Control Y, "unitary" = Arbitrary unitary gate defined as a matrix equivalent to the MatrixGate in + ProjectQ, "xy" = Not available in ProjectQ Args: cmd (Command): Command for which to check availability @@ -264,12 +249,11 @@ def _reset(self): self._clear = True self._measured_ids = [] - def _store(self, cmd): + def _store(self, cmd): # pylint: disable=too-many-branches """ Temporarily store the command cmd. - Translates the command and stores it in a local variable - (self._circuit) in JSON format. + Translates the command and stores it in a local variable (self._circuit) in JSON format. Args: cmd: Command to store @@ -281,7 +265,9 @@ def _store(self, cmd): return num_controls = get_control_count(cmd) - gate_type = type(gate) if not isinstance(gate, DaggeredGate) else type(gate._gate) + gate_type = ( + type(gate) if not isinstance(gate, DaggeredGate) else type(gate._gate) # pylint: disable=protected-access + ) if self._clear: self._probabilities = dict() @@ -293,7 +279,6 @@ def _store(self, cmd): self._allocated_qubits.add(cmd.qubits[0][0].id) return if gate == Measure: - assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 qb_id = cmd.qubits[0][0].id logical_id = None for tag in cmd.tags: @@ -343,22 +328,21 @@ def _logical_to_physical(self, qb_id): mapping = self.main_engine.mapper.current_mapping if qb_id not in mapping: raise RuntimeError( - "Unknown qubit id {} in current mapping. Please make sure " - "eng.flush() was called and that the qubit " - "was eliminated during optimization.".format(qb_id) + ( + "Unknown qubit id {} in current mapping. Please make sure eng.flush() was called and that the" + "qubit was eliminated during optimization." + ).format(qb_id) ) return mapping[qb_id] return 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. + 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 - the first qubit in the supplied quantum register. + The measured bits are ordered according to the supplied quantum register, i.e., the left-most bit in the + state-string corresponds to the first qubit in the supplied quantum register. Args: qureg (list): Quantum register determining the order of the @@ -376,16 +360,13 @@ def get_probabilities(self, qureg): Warning: Only call this function after the circuit has been executed! - This is maintained in the same form of IBM and AQT for - compatibility but in AWSBraket, a previously executed circuit will - store the results in the S3 bucket and it can be retreived at any - point in time thereafter. - No circuit execution should be required at the time of retrieving - the results and probabilities if the circuit has already been - executed. - In order to obtain the probabilities of a previous job you have to - get the TaskArn and remember the qubits and ordering used in the - original job. + This is maintained in the same form of IBM and AQT for compatibility but in AWSBraket, a previously + executed circuit will store the results in the S3 bucket and it can be retreived at any point in time + thereafter. + No circuit execution should be required at the time of retrieving the results and probabilities if the + circuit has already been executed. + In order to obtain the probabilities of a previous job you have to get the TaskArn and remember the qubits + and ordering used in the original job. """ if len(self._probabilities) == 0: @@ -394,9 +375,10 @@ def get_probabilities(self, qureg): probability_dict = dict() for state in self._probabilities: mapped_state = ['0'] * len(qureg) - for i, _ in enumerate(qureg): - assert self._logical_to_physical(qureg[i].id) < len(state) - mapped_state[i] = state[self._logical_to_physical(qureg[i].id)] + for i, qubit in enumerate(qureg): + if self._logical_to_physical(qubit.id) >= len(state): # pragma: no cover + raise IndexError('Physical ID {} > length of internal probabilities array'.format(qubit.id)) + mapped_state[i] = state[self._logical_to_physical(qubit.id)] probability = self._probabilities[state] mapped_state = "".join(mapped_state) if mapped_state not in probability_dict: @@ -409,26 +391,23 @@ def _run(self): """ Run the circuit. - Send the circuit via the AWS Boto3 SDK. Use the provided Access Key and - Secret key or ask for them if not provided + Send the circuit via the AWS Boto3 SDK. Use the provided Access Key and Secret key or ask for them if not + provided """ - # NB: the AWS Braket API does not require explicit measurement commands - # at the end of a circuit; after running any circuit, all qubits are - # implicitly measured. - # Also, AWS Braket currently does not support intermediate + # NB: the AWS Braket API does not require explicit measurement commands at the end of a circuit; after running + # any circuit, all qubits are implicitly measured. Also, AWS Braket currently does not support intermediate # measurements. # If the clear flag is set, nothing to do here... if self._clear: return - # In Braket the results for the jobs are stored in S3. - # You can recover the results from previous jobs using the TaskArn - # (self._retrieve_execution). + # In Braket the results for the jobs are stored in S3. You can recover the results from previous jobs using + # the TaskArn (self._retrieve_execution). if self._retrieve_execution is not None: res = retrieve( credentials=self._credentials, - taskArn=self._retrieve_execution, + task_arn=self._retrieve_execution, num_retries=self._num_retries, interval=self._interval, verbose=self._verbose, @@ -457,14 +436,14 @@ def _run(self): counts = res # Determine random outcome - P = random.random() + random_outcome = random.random() p_sum = 0.0 measured = "" for state in counts: probability = counts[state] p_sum += probability star = "" - if p_sum >= P and measured == "": + if p_sum >= random_outcome and measured == "": measured = state star = "*" self._probabilities[state] = probability @@ -479,8 +458,7 @@ def _run(self): def receive(self, command_list): """ - Receives a command list and, for each command, stores it until - completion. + Receives a command list and, for each command, stores it until completion. Args: command_list: List of commands to execute diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py index 533557653..392d1af0e 100755 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client.py @@ -21,14 +21,14 @@ """ import getpass -import signal +import json import re +import signal import time + import boto3 import botocore -import json - class AWSBraket: """ @@ -41,7 +41,7 @@ def __init__(self): self._credentials = dict() self._s3_folder = [] - def _authenticate(self, credentials=None): + def authenticate(self, credentials=None): """ Args: credentials (dict): mapping the AWS key credentials as the @@ -53,16 +53,16 @@ def _authenticate(self, credentials=None): self._credentials = credentials - def _get_s3_folder(self, s3_folder=None): + def get_s3_folder(self, s3_folder=None): """ Args: s3_folder (list): contains the S3 bucket and directory to store the results. """ if s3_folder is None: # pragma: no cover - S3Bucket = input("Enter the S3 Bucket configured in Braket: ") - S3Directory = input("Enter the Directory created in the S3 Bucket: ") - s3_folder = [S3Bucket, S3Directory] + s3_bucket = input("Enter the S3 Bucket configured in Braket: ") + s3_directory = input("Enter the Directory created in the S3 Bucket: ") + s3_folder = [s3_bucket, s3_directory] self._s3_folder = s3_folder @@ -96,38 +96,38 @@ def get_list_devices(self, verbose=False): if result['deviceType'] not in ['QPU', 'SIMULATOR']: continue if result['deviceType'] == 'QPU': - deviceCapabilities = json.loads( + device_capabilities = json.loads( client.get_device(deviceArn=result['deviceArn'])['deviceCapabilities'] ) self.backends[result['deviceName']] = { - 'nq': deviceCapabilities['paradigm']['qubitCount'], - 'coupling_map': deviceCapabilities['paradigm']['connectivity']['connectivityGraph'], - 'version': deviceCapabilities['braketSchemaHeader']['version'], + 'nq': device_capabilities['paradigm']['qubitCount'], + 'coupling_map': device_capabilities['paradigm']['connectivity']['connectivityGraph'], + 'version': device_capabilities['braketSchemaHeader']['version'], 'location': region, # deviceCapabilities['service']['deviceLocation'], 'deviceArn': result['deviceArn'], - 'deviceParameters': deviceCapabilities['deviceParameters']['properties']['braketSchemaHeader'][ + 'deviceParameters': device_capabilities['deviceParameters']['properties']['braketSchemaHeader'][ 'const' ], - 'deviceModelParameters': deviceCapabilities['deviceParameters']['definitions'][ + 'deviceModelParameters': device_capabilities['deviceParameters']['definitions'][ 'GateModelParameters' ]['properties']['braketSchemaHeader']['const'], } # Unfortunatelly the Capabilities schemas are not homogeneus # for real devices and simulators elif result['deviceType'] == 'SIMULATOR': - deviceCapabilities = json.loads( + device_capabilities = json.loads( client.get_device(deviceArn=result['deviceArn'])['deviceCapabilities'] ) self.backends[result['deviceName']] = { - 'nq': deviceCapabilities['paradigm']['qubitCount'], + 'nq': device_capabilities['paradigm']['qubitCount'], 'coupling_map': {}, - 'version': deviceCapabilities['braketSchemaHeader']['version'], + 'version': device_capabilities['braketSchemaHeader']['version'], 'location': 'us-east-1', 'deviceArn': result['deviceArn'], - 'deviceParameters': deviceCapabilities['deviceParameters']['properties']['braketSchemaHeader'][ + 'deviceParameters': device_capabilities['deviceParameters']['properties']['braketSchemaHeader'][ 'const' ], - 'deviceModelParameters': deviceCapabilities['deviceParameters']['definitions'][ + 'deviceModelParameters': device_capabilities['deviceParameters']['definitions'][ 'GateModelParameters' ]['properties']['braketSchemaHeader']['const'], } @@ -170,7 +170,7 @@ def can_run_experiment(self, info, device): nb_qubit_needed = info['nq'] return nb_qubit_needed <= nb_qubit_max, nb_qubit_max, nb_qubit_needed - def _run(self, info, device): + def run(self, info, device): """ Run the quantum code to the AWS Braket selected device. @@ -180,7 +180,7 @@ def _run(self, info, device): device (str): name of the device to use Returns: - taskArn (str): The Arn of the task + task_arn (str): The Arn of the task """ @@ -219,8 +219,10 @@ def _run(self, info, device): return response['quantumTaskArn'] - def _get_result(self, execution_id, num_retries=30, interval=1, verbose=False): - + def get_result(self, execution_id, num_retries=30, interval=1, verbose=False): # pylint: disable=too-many-locals + """ + Get the result of an execution + """ if verbose: print("Waiting for results. [Job Arn: {}]".format(execution_id)) @@ -248,14 +250,14 @@ def _calculate_measurement_probs(measurements): measurements_probabilities = {} for i in range(total_unique_mes): strqubits = '' - for nq in range(len_qubits): - strqubits += str(unique_mes[i][nq]) + for qubit_idx in range(len_qubits): + strqubits += str(unique_mes[i][qubit_idx]) prob = measurements.count(unique_mes[i]) / total_mes measurements_probabilities[strqubits] = prob return measurements_probabilities - # The region_name is obtained from the taskArn itself + # The region_name is obtained from the task_arn itself region_name = re.split(':', execution_id)[3] client_braket = boto3.client( 'braket', @@ -321,11 +323,11 @@ def _calculate_measurement_probs(measurements): class DeviceTooSmall(Exception): - pass + """Exception raised if the device is too small to run the circuit""" class DeviceOfflineError(Exception): - pass + """Exception raised if a selected device is currently offline""" def show_devices(credentials=None, verbose=False): @@ -342,21 +344,21 @@ def show_devices(credentials=None, verbose=False): (list) list of available devices and their properties """ awsbraket_session = AWSBraket() - awsbraket_session._authenticate(credentials=credentials) + awsbraket_session.authenticate(credentials=credentials) return awsbraket_session.get_list_devices(verbose=verbose) # TODO: Create a Show Online properties per device -def retrieve(credentials, taskArn, num_retries=30, interval=1, verbose=False): +def retrieve(credentials, task_arn, num_retries=30, interval=1, verbose=False): """ Retrieves a job/task by its Arn. Args: credentials (dict): Dictionary storing the AWS credentials with keys AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. - taskArn (str): The Arn of the task to retreive + task_arn (str): The Arn of the task to retreive Returns: (dict) measurement probabilities from the result @@ -368,18 +370,20 @@ def retrieve(credentials, taskArn, num_retries=30, interval=1, verbose=False): print("- Authenticating...") if credentials is not None: print("AWS credentials: " + credentials['AWS_ACCESS_KEY_ID'] + ", " + credentials['AWS_SECRET_KEY']) - awsbraket_session._authenticate(credentials=credentials) - res = awsbraket_session._get_result(taskArn, num_retries=num_retries, interval=interval, verbose=verbose) + awsbraket_session.authenticate(credentials=credentials) + res = awsbraket_session.get_result(task_arn, num_retries=num_retries, interval=interval, verbose=verbose) return res except botocore.exceptions.ClientError as error: error_code = error.response['Error']['Code'] if error_code == 'ResourceNotFoundException': - print("- Unable to locate the job with Arn ", taskArn) + print("- Unable to locate the job with Arn ", task_arn) print(error, error_code) raise -def send(info, device, credentials, s3_folder, num_retries=30, interval=1, verbose=False): +def send( # pylint: disable=too-many-branches,too-many-arguments,too-many-locals + info, device, credentials, s3_folder, num_retries=30, interval=1, verbose=False +): """ Sends cicruit through the Boto3 SDK and runs the quantum circuit. @@ -404,8 +408,8 @@ def send(info, device, credentials, s3_folder, num_retries=30, interval=1, verbo print("- Authenticating...") if credentials is not None: print("AWS credentials: " + credentials['AWS_ACCESS_KEY_ID'] + ", " + credentials['AWS_SECRET_KEY']) - awsbraket_session._authenticate(credentials=credentials) - awsbraket_session._get_s3_folder(s3_folder=s3_folder) + awsbraket_session.authenticate(credentials=credentials) + awsbraket_session.get_s3_folder(s3_folder=s3_folder) # check if the device is online/is available awsbraket_session.get_list_devices(verbose) @@ -429,12 +433,12 @@ def send(info, device, credentials, s3_folder, num_retries=30, interval=1, verbo raise DeviceTooSmall("Device is too small.") if verbose: print("- Running code: {}".format(info)) - taskArn = awsbraket_session._run(info, device) - print("Your task Arn is: {}. Make note of that for future reference".format(taskArn)) + task_arn = awsbraket_session.run(info, device) + print("Your task Arn is: {}. Make note of that for future reference".format(task_arn)) if verbose: print("- Waiting for results...") - res = awsbraket_session._get_result(taskArn, num_retries=num_retries, interval=interval, verbose=verbose) + res = awsbraket_session.get_result(task_arn, num_retries=num_retries, interval=interval, verbose=verbose) if verbose: print("- Done.") return res diff --git a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py index 5faf939a8..267e982ca 100644 --- a/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py +++ b/projectq/backends/_awsbraket/_awsbraket_boto3_client_test.py @@ -103,11 +103,11 @@ def test_retrieve(mocker, var_status, var_result, retrieve_setup): mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) if var_status == 'completed': - res = _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask) + res = _awsbraket_boto3_client.retrieve(credentials=creds, task_arn=arntask) assert res == res_completed else: with pytest.raises(Exception) as exinfo: - _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask, num_retries=2) + _awsbraket_boto3_client.retrieve(credentials=creds, task_arn=arntask, num_retries=2) print(exinfo.value) if var_status == 'failed': assert ( @@ -146,7 +146,7 @@ def test_retrieve_devicetypes(mocker, retrieve_devicetypes_setup): mock_boto3_client.get_object.return_value = results_dict mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) - res = _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask) + res = _awsbraket_boto3_client.retrieve(credentials=creds, task_arn=arntask) assert res == res_completed @@ -294,7 +294,7 @@ def test_retrieve_error_arn_not_exist(mocker, var_error, arntask, creds): mocker.patch('boto3.client', return_value=mock_boto3_client, autospec=True) with pytest.raises(botocore.exceptions.ClientError): - _awsbraket_boto3_client.retrieve(credentials=creds, taskArn=arntask) + _awsbraket_boto3_client.retrieve(credentials=creds, task_arn=arntask) # ============================================================================== diff --git a/projectq/backends/_circuits/__init__.py b/projectq/backends/_circuits/__init__.py index 79ce3a298..8985f0fe0 100755 --- a/projectq/backends/_circuits/__init__.py +++ b/projectq/backends/_circuits/__init__.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ProjectQ module for exporting/printing quantum circuits""" + from ._to_latex import to_latex from ._plot import to_draw diff --git a/projectq/backends/_circuits/_drawer.py b/projectq/backends/_circuits/_drawer.py index fd86c92a4..bea56f40b 100755 --- a/projectq/backends/_circuits/_drawer.py +++ b/projectq/backends/_circuits/_drawer.py @@ -21,10 +21,12 @@ from projectq.cengines import LastEngineException, BasicEngine from projectq.ops import FlushGate, Measure, Allocate, Deallocate from projectq.meta import get_control_count -from projectq.backends._circuits import to_latex +from ._to_latex import to_latex -class CircuitItem(object): +class CircuitItem: + """Item of a quantum circuit to draw""" + def __init__(self, gate, lines, ctrl_lines): """ Initialize a circuit item. @@ -216,6 +218,7 @@ def _print_cmd(self, cmd): Args: cmd (Command): Command to add to the circuit diagram. """ + # pylint: disable=R0801 if cmd.gate == Allocate: qubit_id = cmd.qubits[0][0].id if qubit_id not in self._map: @@ -227,19 +230,20 @@ def _print_cmd(self, cmd): self._free_lines.append(qubit_id) if self.is_last_engine and cmd.gate == Measure: - assert get_control_count(cmd) == 0 + if get_control_count(cmd) != 0: + raise ValueError('Cannot have control qubits with a measurement gate!') for qureg in cmd.qubits: for qubit in qureg: if self._accept_input: - m = None - while m not in ('0', '1', 1, 0): + meas = None + while meas not in ('0', '1', 1, 0): prompt = "Input measurement result (0 or 1) for qubit " + str(qubit) + ": " - m = input(prompt) + meas = input(prompt) else: - m = self._default_measure - m = int(m) - self.main_engine.set_measurement_result(qubit, m) + meas = self._default_measure + meas = int(meas) + self.main_engine.set_measurement_result(qubit, meas) all_lines = [qb.id for qr in cmd.all_qubits for qb in qr] diff --git a/projectq/backends/_circuits/_drawer_matplotlib.py b/projectq/backends/_circuits/_drawer_matplotlib.py index c26bd0af4..ee83c6023 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib.py +++ b/projectq/backends/_circuits/_drawer_matplotlib.py @@ -24,7 +24,7 @@ from projectq.cengines import LastEngineException, BasicEngine from projectq.ops import FlushGate, Measure, Allocate, Deallocate from projectq.meta import get_control_count -from projectq.backends._circuits import to_draw +from ._plot import to_draw # ============================================================================== @@ -98,7 +98,7 @@ def is_available(self, cmd): except LastEngineException: return True - def _process(self, cmd): + def _process(self, cmd): # pylint: disable=too-many-branches """ Process the command cmd and stores it in the internal storage @@ -109,18 +109,20 @@ def _process(self, cmd): Args: cmd (Command): Command to add to the circuit diagram. """ + # pylint: disable=R0801 if cmd.gate == Allocate: - qubit_id = cmd.qubits[0][0].id - if qubit_id not in self._map: - self._map[qubit_id] = qubit_id - self._qubit_lines[qubit_id] = [] + qb_id = cmd.qubits[0][0].id + if qb_id not in self._map: + self._map[qb_id] = qb_id + self._qubit_lines[qb_id] = [] return if cmd.gate == Deallocate: return if self.is_last_engine and cmd.gate == Measure: - assert get_control_count(cmd) == 0 + if get_control_count(cmd) != 0: + raise ValueError('Cannot have control qubits with a measurement gate!') for qureg in cmd.qubits: for qubit in qureg: if self._accept_input: @@ -152,14 +154,14 @@ def _process(self, cmd): if len(targets) + len(controls) > 1: max_depth = max(len(self._qubit_lines[qubit_id]) for qubit_id in self._qubit_lines) - for qubit_id in itertools.chain(targets, controls): - depth = len(self._qubit_lines[qubit_id]) - self._qubit_lines[qubit_id] += [None] * (max_depth - depth) + for qb_id in itertools.chain(targets, controls): + depth = len(self._qubit_lines[qb_id]) + self._qubit_lines[qb_id] += [None] * (max_depth - depth) - if qubit_id == ref_qubit_id: - self._qubit_lines[qubit_id].append((gate_str, targets, controls)) + if qb_id == ref_qubit_id: + self._qubit_lines[qb_id].append((gate_str, targets, controls)) else: - self._qubit_lines[qubit_id].append(None) + self._qubit_lines[qb_id].append(None) def receive(self, command_list): """ diff --git a/projectq/backends/_circuits/_drawer_matplotlib_test.py b/projectq/backends/_circuits/_drawer_matplotlib_test.py index 600ff051c..9c52ad34f 100644 --- a/projectq/backends/_circuits/_drawer_matplotlib_test.py +++ b/projectq/backends/_circuits/_drawer_matplotlib_test.py @@ -16,6 +16,8 @@ Tests for projectq.backends.circuits._drawer.py. """ +import pytest + from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.ops import H, X, Rx, CNOT, Swap, Measure, Command, BasicGate @@ -49,6 +51,11 @@ def test_drawer_measurement(): assert int(qubit) == 1 _drawer.input = old_input + qb1 = WeakQubitRef(engine=eng, idx=1) + qb2 = WeakQubitRef(engine=eng, idx=2) + with pytest.raises(ValueError): + eng.backend._process(Command(engine=eng, gate=Measure, qubits=([qb1],), controls=[qb2])) + class MockEngine(object): def is_available(self, cmd): diff --git a/projectq/backends/_circuits/_drawer_test.py b/projectq/backends/_circuits/_drawer_test.py index e308bbf45..26ef4974b 100755 --- a/projectq/backends/_circuits/_drawer_test.py +++ b/projectq/backends/_circuits/_drawer_test.py @@ -19,7 +19,8 @@ import pytest from projectq import MainEngine -from projectq.ops import H, X, CNOT, Measure +from projectq.ops import H, X, CNOT, Measure, Command +from projectq.types import WeakQubitRef import projectq.backends._circuits._drawer as _drawer from projectq.backends._circuits._drawer import CircuitItem, CircuitDrawer @@ -80,6 +81,11 @@ def test_drawer_measurement(): assert int(qubit) == 1 _drawer.input = old_input + qb1 = WeakQubitRef(engine=eng, idx=1) + qb2 = WeakQubitRef(engine=eng, idx=2) + with pytest.raises(ValueError): + eng.backend._print_cmd(Command(engine=eng, gate=Measure, qubits=([qb1],), controls=[qb2])) + def test_drawer_qubitmapping(): drawer = CircuitDrawer() diff --git a/projectq/backends/_circuits/_plot.py b/projectq/backends/_circuits/_plot.py index 97db7646c..0d3673fc6 100644 --- a/projectq/backends/_circuits/_plot.py +++ b/projectq/backends/_circuits/_plot.py @@ -296,7 +296,9 @@ def resize_figure(fig, axes, width, height, plot_params): axes.set_ylim(0, new_limits[1]) -def draw_gates(axes, qubit_lines, drawing_order, gate_grid, wire_grid, plot_params): +def draw_gates( # pylint: disable=too-many-arguments + axes, qubit_lines, drawing_order, gate_grid, wire_grid, plot_params +): """ Draws the gates. @@ -326,7 +328,9 @@ def draw_gates(axes, qubit_lines, drawing_order, gate_grid, wire_grid, plot_para ) -def draw_gate(axes, gate_str, gate_pos, target_wires, targets_order, control_wires, plot_params): +def draw_gate( + axes, gate_str, gate_pos, target_wires, targets_order, control_wires, plot_params +): # pylint: disable=too-many-arguments """ Draws a single gate at a given location. @@ -482,7 +486,9 @@ def draw_measure_gate(axes, gate_pos, wire_pos, plot_params): axes.add_collection(gate) -def multi_qubit_gate(axes, gate_str, gate_pos, wire_pos_min, wire_pos_max, plot_params): +def multi_qubit_gate( # pylint: disable=too-many-arguments + axes, gate_str, gate_pos, wire_pos_min, wire_pos_max, plot_params +): """ Draws a multi-target qubit gate. diff --git a/projectq/backends/_circuits/_to_latex.py b/projectq/backends/_circuits/_to_latex.py index 37cc17758..4b1a568fa 100755 --- a/projectq/backends/_circuits/_to_latex.py +++ b/projectq/backends/_circuits/_to_latex.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ProjectQ module for exporting quantum circuits to LaTeX code""" + import json from projectq.ops import ( Allocate, @@ -27,25 +29,41 @@ ) +def _gate_name(gate): + """ + Return the string representation of the gate. + + Tries to use gate.tex_str and, if that is not available, uses str(gate) instead. + + Args: + gate: Gate object of which to get the name / latex representation. + + Returns: + gate_name (string): Latex gate name. + """ + try: + name = gate.tex_str() + except AttributeError: + name = str(gate) + return name + + def to_latex(circuit, drawing_order=None, draw_gates_in_parallel=True): """ Translates a given circuit to a TikZ picture in a Latex document. - It uses a json-configuration file which (if it does not exist) is created - automatically upon running this function for the first time. The config - file can be used to determine custom gate sizes, offsets, etc. + It uses a json-configuration file which (if it does not exist) is created automatically upon running this function + for the first time. The config file can be used to determine custom gate sizes, offsets, etc. - New gate options can be added under settings['gates'], using the gate - class name string as a key. Every gate can have its own width, height, pre - offset and offset. + New gate options can be added under settings['gates'], using the gate class name string as a key. Every gate can + have its own width, height, pre offset and offset. Example: .. code-block:: python settings['gates']['HGate'] = {'width': .5, 'offset': .15} - The default settings can be acquired using the get_default_settings() - function, and written using write_settings(). + The default settings can be acquired using the get_default_settings() function, and written using write_settings(). Args: circuit (list): Each qubit line is a list of @@ -67,7 +85,7 @@ class name string as a key. Every gate can have its own width, height, pre text = _header(settings) text += _body(circuit, settings, drawing_order, draw_gates_in_parallel=draw_gates_in_parallel) - text += _footer(settings) + text += _footer() return text @@ -146,7 +164,7 @@ def _header(settings): "blur,fit,decorations.pathreplacing,shapes}\n\n" ) - init = "\\begin{document}\n" "\\begin{tikzpicture}[scale=0.8, transform shape]\n\n" + init = "\\begin{document}\n\\begin{tikzpicture}[scale=0.8, transform shape]\n\n" gate_style = ( "\\tikzstyle{basicshadow}=[blur shadow={shadow blur steps=8," @@ -199,17 +217,14 @@ def _header(settings): def _body(circuit, settings, drawing_order=None, draw_gates_in_parallel=True): """ - Return the body of the Latex document, including the entire circuit in - TikZ format. + Return the body of the Latex document, including the entire circuit in TikZ format. Args: circuit (list>): Circuit to draw. settings: Dictionary of settings to use for the TikZ image. - drawing_order: A list of circuit wires from where to read - one gate command. - draw_gates_in_parallel: Are the gate/commands occupying a - single time step in the circuit diagram? For example, False means - that gates can be parallel in the circuit. + drawing_order: A list of circuit wires from where to read one gate command. + draw_gates_in_parallel: Are the gate/commands occupying a single time step in the circuit diagram? For example, + False means that gates can be parallel in the circuit. Returns: tex_str (string): Latex string to draw the entire circuit. @@ -237,7 +252,7 @@ def _body(circuit, settings, drawing_order=None, draw_gates_in_parallel=True): return "".join(code) -def _footer(settings): +def _footer(): """ Return the footer of the Latex document. @@ -247,10 +262,9 @@ def _footer(settings): return "\n\n\\end{tikzpicture}\n\\end{document}" -class _Circ2Tikz(object): +class _Circ2Tikz: # pylint: disable=too-few-public-methods """ - The Circ2Tikz class takes a circuit (list of lists of CircuitItem objects) - and turns them into Latex/TikZ code. + The Circ2Tikz class takes a circuit (list of lists of CircuitItem objects) and turns them into Latex/TikZ code. It uses the settings dictionary for gate offsets, sizes, spacing, ... """ @@ -269,14 +283,14 @@ def __init__(self, settings, num_lines): self.op_count = [0] * num_lines self.is_quantum = [settings['lines']['init_quantum']] * num_lines - def to_tikz(self, line, circuit, end=None, draw_gates_in_parallel=True): + def to_tikz( # pylint: disable=too-many-branches,too-many-locals,too-many-statements + self, line, circuit, end=None, draw_gates_in_parallel=True + ): """ - Generate the TikZ code for one line of the circuit up to a certain - gate. + Generate the TikZ code for one line of the circuit up to a certain gate. - It modifies the circuit to include only the gates which have not been - drawn. It automatically switches to other lines if the gates on those - lines have to be drawn earlier. + It modifies the circuit to include only the gates which have not been drawn. It automatically switches to other + lines if the gates on those lines have to be drawn earlier. Args: line (int): Line to generate the TikZ code for. @@ -285,9 +299,8 @@ def to_tikz(self, line, circuit, end=None, draw_gates_in_parallel=True): draw_gates_in_parallel (bool): True or False for how to place gates Returns: - tikz_code (string): TikZ code representing the current qubit line - and, if it was necessary to draw other lines, those lines as - well. + tikz_code (string): TikZ code representing the current qubit line and, if it was necessary to draw other + lines, those lines as well. """ if end is None: end = len(circuit[line]) @@ -302,24 +315,24 @@ def to_tikz(self, line, circuit, end=None, draw_gates_in_parallel=True): all_lines = lines + ctrl_lines all_lines.remove(line) # remove current line - for ll in all_lines: + for _line in all_lines: gate_idx = 0 - while not (circuit[ll][gate_idx] == cmds[i]): + while not circuit[_line][gate_idx] == cmds[i]: gate_idx += 1 - tikz_code.append(self.to_tikz(ll, circuit, gate_idx)) + tikz_code.append(self.to_tikz(_line, circuit, gate_idx)) # we are taking care of gate 0 (the current one) - circuit[ll] = circuit[ll][1:] + circuit[_line] = circuit[_line][1:] all_lines = lines + ctrl_lines pos = max([self.pos[ll] for ll in range(min(all_lines), max(all_lines) + 1)]) - for ll in range(min(all_lines), max(all_lines) + 1): - self.pos[ll] = pos + self._gate_pre_offset(gate) + for _line in range(min(all_lines), max(all_lines) + 1): + self.pos[_line] = pos + self._gate_pre_offset(gate) connections = "" - for ll in all_lines: - connections += self._line(self.op_count[ll] - 1, self.op_count[ll], line=ll) + for _line in all_lines: + connections += self._line(self.op_count[_line] - 1, self.op_count[_line], line=_line) add_str = "" if gate == X: # draw NOT-gate with controls @@ -338,8 +351,8 @@ def to_tikz(self, line, circuit, end=None, draw_gates_in_parallel=True): add_str = self._sqrtswap_gate(lines, ctrl_lines, daggered=True) elif gate == Measure: # draw measurement gate - for ll in lines: - op = self._op(ll) + for _line in lines: + op = self._op(_line) width = self._gate_width(Measure) height = self._gate_height(Measure) shift0 = 0.07 * height @@ -357,15 +370,15 @@ def to_tikz(self, line, circuit, end=None, draw_gates_in_parallel=True): "{shift1}cm]{op}.north east);" ).format( op=op, - pos=self.pos[ll], - line=ll, + pos=self.pos[_line], + line=_line, shift0=shift0, shift1=shift1, shift2=shift2, ) - self.op_count[ll] += 1 - self.pos[ll] += self._gate_width(gate) + self._gate_offset(gate) - self.is_quantum[ll] = False + self.op_count[_line] += 1 + self.pos[_line] += self._gate_width(gate) + self._gate_offset(gate) + self.is_quantum[_line] = False elif gate == Allocate: # draw 'begin line' add_str = "\n\\node[none] ({}) at ({},-{}) {{$\\Ket{{0}}{}$}};" @@ -401,41 +414,22 @@ def to_tikz(self, line, circuit, end=None, draw_gates_in_parallel=True): # regular gate must draw the lines it does not act upon # if it spans multiple qubits add_str = self._regular_gate(gate, lines, ctrl_lines) - for ll in lines: - self.is_quantum[ll] = True + for _line in lines: + self.is_quantum[_line] = True tikz_code.append(add_str) if not gate == Allocate: tikz_code.append(connections) if not draw_gates_in_parallel: - for ll in range(len(self.pos)): - if ll != line: - self.pos[ll] = self.pos[line] + for _line in range(len(self.pos)): + if _line != line: + self.pos[_line] = self.pos[line] circuit[line] = circuit[line][end:] return "".join(tikz_code) - def _gate_name(self, gate): - """ - Return the string representation of the gate. - - Tries to use gate.tex_str and, if that is not available, uses str(gate) - instead. - - Args: - gate: Gate object of which to get the name / latex representation. - - Returns: - gate_name (string): Latex gate name. - """ - try: - name = gate.tex_str() - except AttributeError: - name = str(gate) - return name - - def _sqrtswap_gate(self, lines, ctrl_lines, daggered): + def _sqrtswap_gate(self, lines, ctrl_lines, daggered): # pylint: disable=too-many-locals """ Return the TikZ code for a Square-root Swap-gate. @@ -445,7 +439,8 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): ctrl_lines (list): List of qubit lines which act as controls. daggered (bool): Show the daggered one if True. """ - assert len(lines) == 2 # sqrt swap gate acts on 2 qubits + if len(lines) != 2: + raise RuntimeError('Sqrt SWAP gate acts on 2 qubits') delta_pos = self._gate_offset(SqrtSwap) gate_width = self._gate_width(SqrtSwap) lines.sort() @@ -453,11 +448,11 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): gate_str = "" for line in lines: op = self._op(line) - w = "{}cm".format(0.5 * gate_width) - s1 = "[xshift=-{w},yshift=-{w}]{op}.center".format(w=w, op=op) - s2 = "[xshift={w},yshift={w}]{op}.center".format(w=w, op=op) - s3 = "[xshift=-{w},yshift={w}]{op}.center".format(w=w, op=op) - s4 = "[xshift={w},yshift=-{w}]{op}.center".format(w=w, op=op) + width = "{}cm".format(0.5 * gate_width) + blc = "[xshift=-{w},yshift=-{w}]{op}.center".format(w=width, op=op) + trc = "[xshift={w},yshift={w}]{op}.center".format(w=width, op=op) + tlc = "[xshift=-{w},yshift={w}]{op}.center".format(w=width, op=op) + brc = "[xshift={w},yshift=-{w}]{op}.center".format(w=width, op=op) swap_style = "swapstyle,edgestyle" if self.settings['gate_shadow']: swap_style += ",shadowed" @@ -467,10 +462,10 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): "\\draw[{swap_style}] ({s3})--({s4});" ).format( op=op, - s1=s1, - s2=s2, - s3=s3, - s4=s4, + s1=blc, + s2=trc, + s3=tlc, + s4=brc, line=line, pos=self.pos[line], swap_style=swap_style, @@ -511,7 +506,7 @@ def _sqrtswap_gate(self, lines, ctrl_lines, daggered): self.pos[i] = new_pos return gate_str - def _swap_gate(self, lines, ctrl_lines): + def _swap_gate(self, lines, ctrl_lines): # pylint: disable=too-many-locals """ Return the TikZ code for a Swap-gate. @@ -521,7 +516,8 @@ def _swap_gate(self, lines, ctrl_lines): ctrl_lines (list): List of qubit lines which act as controls. """ - assert len(lines) == 2 # swap gate acts on 2 qubits + if len(lines) != 2: + raise RuntimeError('SWAP gate acts on 2 qubits') delta_pos = self._gate_offset(Swap) gate_width = self._gate_width(Swap) lines.sort() @@ -529,11 +525,11 @@ def _swap_gate(self, lines, ctrl_lines): gate_str = "" for line in lines: op = self._op(line) - w = "{}cm".format(0.5 * gate_width) - s1 = "[xshift=-{w},yshift=-{w}]{op}.center".format(w=w, op=op) - s2 = "[xshift={w},yshift={w}]{op}.center".format(w=w, op=op) - s3 = "[xshift=-{w},yshift={w}]{op}.center".format(w=w, op=op) - s4 = "[xshift={w},yshift=-{w}]{op}.center".format(w=w, op=op) + width = "{}cm".format(0.5 * gate_width) + blc = "[xshift=-{w},yshift=-{w}]{op}.center".format(w=width, op=op) + trc = "[xshift={w},yshift={w}]{op}.center".format(w=width, op=op) + tlc = "[xshift=-{w},yshift={w}]{op}.center".format(w=width, op=op) + brc = "[xshift={w},yshift=-{w}]{op}.center".format(w=width, op=op) swap_style = "swapstyle,edgestyle" if self.settings['gate_shadow']: swap_style += ",shadowed" @@ -543,10 +539,10 @@ def _swap_gate(self, lines, ctrl_lines): "\\draw[{swap_style}] ({s3})--({s4});" ).format( op=op, - s1=s1, - s2=s2, - s3=s3, - s4=s4, + s1=blc, + s2=trc, + s3=tlc, + s4=brc, line=line, pos=self.pos[line], swap_style=swap_style, @@ -580,7 +576,8 @@ def _x_gate(self, lines, ctrl_lines): ctrl_lines (list): List of qubit lines which act as controls. """ - assert len(lines) == 1 # NOT gate only acts on 1 qubit + if len(lines) != 1: + raise RuntimeError('X gate acts on 1 qubits') line = lines[0] delta_pos = self._gate_offset(X) gate_width = self._gate_width(X) @@ -611,7 +608,6 @@ def _cz_gate(self, lines): Args: lines (list): List of all qubits involved. """ - assert len(lines) > 1 line = lines[0] delta_pos = self._gate_offset(Z) gate_width = self._gate_width(Z) @@ -637,7 +633,7 @@ def _gate_width(self, gate): (settings['gates'][gate_class_name]['width']) """ if isinstance(gate, DaggeredGate): - gate = gate._gate + gate = gate._gate # pylint: disable=protected-access try: gates = self.settings['gates'] gate_width = gates[gate.__class__.__name__]['width'] @@ -654,7 +650,7 @@ def _gate_pre_offset(self, gate): (settings['gates'][gate_class_name]['pre_offset']) """ if isinstance(gate, DaggeredGate): - gate = gate._gate + gate = gate._gate # pylint: disable=protected-access try: gates = self.settings['gates'] delta_pos = gates[gate.__class__.__name__]['pre_offset'] @@ -672,7 +668,7 @@ def _gate_offset(self, gate): (settings['gates'][gate_class_name]['offset']) """ if isinstance(gate, DaggeredGate): - gate = gate._gate + gate = gate._gate # pylint: disable=protected-access try: gates = self.settings['gates'] delta_pos = gates[gate.__class__.__name__]['offset'] @@ -689,7 +685,7 @@ def _gate_height(self, gate): (settings['gates'][gate_class_name]['height']) """ if isinstance(gate, DaggeredGate): - gate = gate._gate + gate = gate._gate # pylint: disable=protected-access try: height = self.settings['gates'][gate.__class__.__name__]['height'] except KeyError: @@ -727,11 +723,10 @@ def _op(self, line, op=None, offset=0): op = self.op_count[line] return "line{}_gate{}".format(line, op + offset) - def _line(self, p1, p2, double=False, line=None): + def _line(self, point1, point2, double=False, line=None): # pylint: disable=too-many-locals,unused-argument """ - Connects p1 and p2, where p1 and p2 are either to qubit line indices, - in which case the two most recent gates are connected, or two gate - indices, in which case line denotes the line number and the two gates + Connects point1 and point2, where point1 and point2 are either to qubit line indices, in which case the two most + recent gates are connected, or two gate indices, in which case line denotes the line number and the two gates are connected on the given line. Args: @@ -747,30 +742,30 @@ def _line(self, p1, p2, double=False, line=None): dbl_classical = self.settings['lines']['double_classical'] if line is None: - quantum = not dbl_classical or self.is_quantum[p1] - op1, op2 = self._op(p1), self._op(p2) + quantum = not dbl_classical or self.is_quantum[point1] + op1, op2 = self._op(point1), self._op(point2) loc1, loc2 = 'north', 'south' shift = "xshift={}cm" else: quantum = not dbl_classical or self.is_quantum[line] - op1, op2 = self._op(line, p1), self._op(line, p2) + op1, op2 = self._op(line, point1), self._op(line, point2) loc1, loc2 = 'west', 'east' shift = "yshift={}cm" if quantum: return "\n\\draw ({}) edge[edgestyle] ({});".format(op1, op2) - else: - if p2 > p1: - loc1, loc2 = loc2, loc1 - edge_str = "\n\\draw ([{shift}]{op1}.{loc1}) edge[edgestyle] ([{shift}]{op2}.{loc2});" - line_sep = self.settings['lines']['double_lines_sep'] - shift1 = shift.format(line_sep / 2.0) - shift2 = shift.format(-line_sep / 2.0) - edges_str = edge_str.format(shift=shift1, op1=op1, op2=op2, loc1=loc1, loc2=loc2) - edges_str += edge_str.format(shift=shift2, op1=op1, op2=op2, loc1=loc1, loc2=loc2) - return edges_str - - def _regular_gate(self, gate, lines, ctrl_lines): + + if point2 > point1: + loc1, loc2 = loc2, loc1 + edge_str = "\n\\draw ([{shift}]{op1}.{loc1}) edge[edgestyle] ([{shift}]{op2}.{loc2});" + line_sep = self.settings['lines']['double_lines_sep'] + shift1 = shift.format(line_sep / 2.0) + shift2 = shift.format(-line_sep / 2.0) + edges_str = edge_str.format(shift=shift1, op1=op1, op2=op2, loc1=loc1, loc2=loc2) + edges_str += edge_str.format(shift=shift2, op1=op1, op2=op2, loc1=loc1, loc2=loc2) + return edges_str + + def _regular_gate(self, gate, lines, ctrl_lines): # pylint: disable=too-many-locals """ Draw a regular gate. @@ -792,7 +787,7 @@ def _regular_gate(self, gate, lines, ctrl_lines): gate_width = self._gate_width(gate) gate_height = self._gate_height(gate) - name = self._gate_name(gate) + name = _gate_name(gate) lines = list(range(imin, imax + 1)) diff --git a/projectq/backends/_circuits/_to_latex_test.py b/projectq/backends/_circuits/_to_latex_test.py index c6032109a..2d2246114 100755 --- a/projectq/backends/_circuits/_to_latex_test.py +++ b/projectq/backends/_circuits/_to_latex_test.py @@ -18,6 +18,8 @@ import copy +import pytest + from projectq import MainEngine from projectq.ops import ( BasicGate, @@ -45,7 +47,7 @@ def test_tolatex(): _to_latex._header = lambda x: "H" _to_latex._body = lambda x, settings, drawing_order, draw_gates_in_parallel: x - _to_latex._footer = lambda x: "F" + _to_latex._footer = lambda: "F" latex = _to_latex.to_latex("B") assert latex == "HBF" @@ -183,6 +185,27 @@ def test_body(): assert code.count("{red}") == 3 +@pytest.mark.parametrize('gate, n_qubits', ((SqrtSwap, 3), (Swap, 3), (X, 2)), ids=str) +def test_invalid_number_of_qubits(gate, n_qubits): + drawer = _drawer.CircuitDrawer() + eng = MainEngine(drawer, []) + old_tolatex = _drawer.to_latex + _drawer.to_latex = lambda x, drawing_order, draw_gates_in_parallel: x + + qureg = eng.allocate_qureg(n_qubits) + + gate | (*qureg,) + eng.flush() + + circuit_lines = drawer.get_latex() + _drawer.to_latex = old_tolatex + settings = _to_latex.get_default_settings() + settings['gates']['AllocateQubitGate']['draw_id'] = True + + with pytest.raises(RuntimeError): + _to_latex._body(circuit_lines, settings) + + def test_body_with_drawing_order_and_gates_parallel(): drawer = _drawer.CircuitDrawer() eng = MainEngine(drawer, []) diff --git a/projectq/backends/_ibm/__init__.py b/projectq/backends/_ibm/__init__.py index 216a0e418..21c3b1789 100755 --- a/projectq/backends/_ibm/__init__.py +++ b/projectq/backends/_ibm/__init__.py @@ -13,4 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ProjectQ module for supporting the IBM QE platform""" + from ._ibm import IBMBackend diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index c34057817..a020d2a80 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -19,11 +19,12 @@ from projectq.cengines import BasicEngine from projectq.meta import get_control_count, LogicalQubitIDTag, has_negative_control from projectq.ops import NOT, H, Rx, Ry, Rz, Measure, Allocate, Deallocate, Barrier, FlushGate +from projectq.types import WeakQubitRef from ._ibm_http_client import send, retrieve -class IBMBackend(BasicEngine): +class IBMBackend(BasicEngine): # pylint: disable=too-many-instance-attributes """ The IBM Backend class, which stores the circuit, transforms it to JSON, and sends the circuit through the IBM API. @@ -39,7 +40,7 @@ def __init__( num_retries=3000, interval=1, retrieve_execution=None, - ): + ): # pylint: disable=too-many-arguments """ Initialize the Backend object. @@ -61,7 +62,8 @@ def __init__( retrieve_execution (int): Job ID to retrieve instead of re- running the circuit (e.g., if previous run timed out). """ - BasicEngine.__init__(self) + super().__init__() + self._clear = False self._reset() if use_hardware: self.device = device @@ -92,17 +94,12 @@ def is_available(self, cmd): if has_negative_control(cmd): return False - g = cmd.gate + gate = cmd.gate - if g == NOT and get_control_count(cmd) == 1: - return True + if get_control_count(cmd) == 1: + return gate == NOT if get_control_count(cmd) == 0: - if g == H: - return True - if isinstance(g, (Rx, Ry, Rz)): - return True - if g in (Measure, Allocate, Deallocate, Barrier): - return True + return gate == H or isinstance(gate, (Rx, Ry, Rz)) or gate in (Measure, Allocate, Deallocate, Barrier) return False def get_qasm(self): @@ -115,7 +112,7 @@ def _reset(self): self._clear = True self._measured_ids = [] - def _store(self, cmd): + def _store(self, cmd): # pylint: disable=too-many-branches,too-many-statements """ Temporarily store the command cmd. @@ -124,6 +121,9 @@ def _store(self, cmd): Args: cmd: Command to store """ + if self.main_engine.mapper is None: + raise RuntimeError('No mapper is present in the compiler engine list!') + if self._clear: self._probabilities = dict() self._clear = False @@ -140,13 +140,13 @@ def _store(self, cmd): return if gate == Measure: - assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 logical_id = None - for t in cmd.tags: - if isinstance(t, LogicalQubitIDTag): - logical_id = t.logical_qubit_id + for tag in cmd.tags: + if isinstance(tag, LogicalQubitIDTag): + logical_id = tag.logical_qubit_id break - assert logical_id is not None + if logical_id is None: + raise RuntimeError('No LogicalQubitIDTag found in command!') self._measured_ids += [logical_id] elif gate == NOT and get_control_count(cmd) == 1: ctrl_pos = cmd.control_qubits[0].id @@ -162,7 +162,6 @@ def _store(self, cmd): 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({})'} u_name = {'Rx': 'u3', 'Ry': 'u3', 'Rz': 'u1'} @@ -177,7 +176,6 @@ def _store(self, cmd): 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]}) @@ -192,7 +190,6 @@ def _logical_to_physical(self, qb_id): qb_id (int): ID of the logical qubit whose position should be returned. """ - assert self.main_engine.mapper is not None mapping = self.main_engine.mapper.current_mapping if qb_id not in mapping: raise RuntimeError( @@ -234,8 +231,8 @@ def get_probabilities(self, qureg): 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)] + for i, val in enumerate(qureg): + mapped_state[i] = state[self._logical_to_physical(val.id)] probability = self._probabilities[state] mapped_state = "".join(mapped_state) if mapped_state not in probability_dict: @@ -244,7 +241,7 @@ def get_probabilities(self, qureg): probability_dict[mapped_state] += probability return probability_dict - def _run(self): + def _run(self): # pylint: disable=too-many-locals """ Run the circuit. @@ -254,7 +251,7 @@ def _run(self): # 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.qasm += "\nmeasure q[{0}] -> c[{0}];".format(qb_loc) self._json.append({'qubits': [qb_loc], 'name': 'measure', 'memory': [qb_loc]}) # return if no operations / measurements have been performed. if self.qasm == "": @@ -288,7 +285,7 @@ def _run(self): ) counts = res['data']['counts'] # Determine random outcome - P = random.random() + random_outcome = random.random() p_sum = 0.0 measured = "" for state in counts: @@ -299,30 +296,26 @@ def _run(self): state = state[::-1] p_sum += probability star = "" - if p_sum >= P and measured == "": + if p_sum >= random_outcome and measured == "": measured = state star = "*" self._probabilities[state] = probability if self._verbose and probability > 0: print(str(state) + " with p = " + str(probability) + star) - class QB: - def __init__(self, ID): - self.id = ID - - # register measurement result - for ID in self._measured_ids: - location = self._logical_to_physical(ID) + # register measurement result from IBM + for qubit_id in self._measured_ids: + location = self._logical_to_physical(qubit_id) result = int(measured[location]) - self.main_engine.set_measurement_result(QB(ID), result) + self.main_engine.set_measurement_result(WeakQubitRef(self, qubit_id), result) self._reset() - except TypeError: - raise Exception("Failed to run the circuit. Aborting.") + except TypeError as err: + raise Exception("Failed to run the circuit. Aborting.") from err def receive(self, command_list): """ - Receives a command list and, for each command, stores it until - completion. + Receives a command list and, for each command, stores it until completion. Upon flush, send the data to the + IBM QE API. Args: command_list: List of commands to execute diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 83d8ec58d..323256de2 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -13,6 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" Back-end to run quantum program on IBM QE cloud platform""" + + # helpers to run the jsonified gate sequence on ibm quantum experience server # api documentation does not exist and has to be deduced from the qiskit code # source at: https://github.com/Qiskit/qiskit-ibmq-provider @@ -39,7 +42,7 @@ class IBMQ(Session): """ def __init__(self, **kwargs): - super(IBMQ, self).__init__(**kwargs) # Python 2 compatibility + super().__init__(**kwargs) self.backends = dict() self.timeout = 5.0 @@ -57,7 +60,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)} - request = super(IBMQ, self).get(urljoin(_API_URL, list_device_url), **argument) + request = super().get(urljoin(_API_URL, list_device_url), **argument) request.raise_for_status() r_json = request.json() self.backends = dict() @@ -104,7 +107,7 @@ def can_run_experiment(self, info, device): 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. @@ -119,12 +122,12 @@ def _authenticate(self, token=None): 'json': {'apiToken': token}, 'timeout': (self.timeout, None), } - request = super(IBMQ, self).post(_AUTH_API_URL, **args) + request = super().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): + def run(self, info, device): # pylint: disable=too-many-locals """ Run the quantum code to the IBMQ machine. Update since September 2020: only protocol available is what they call @@ -153,7 +156,7 @@ def _run(self, info, device): }, 'timeout': (self.timeout, None), } - request = super(IBMQ, self).post( + request = super().post( urljoin(_API_URL, 'Network/ibm-q/Groups/open/Projects/main/Jobs'), **json_step1, ) @@ -196,7 +199,7 @@ def _run(self, info, device): 'params': {'access_token': None}, 'timeout': (5.0, None), } - request = super(IBMQ, self).put(upload_url, **json_step2) + request = super().put(upload_url, **json_step2) request.raise_for_status() # STEP3: CONFIRM UPLOAD @@ -206,12 +209,17 @@ def _run(self, info, device): _API_URL, 'Network/ibm-q/Groups/open/Projects/main/Jobs/' + str(execution_id) + '/jobDataUploaded', ) - request = super(IBMQ, self).post(upload_data_url, **json_step3) + request = super().post(upload_data_url, **json_step3) request.raise_for_status() return execution_id - def _get_result(self, device, execution_id, num_retries=3000, interval=1, verbose=False): + def get_result( + self, device, execution_id, num_retries=3000, interval=1, verbose=False + ): # pylint: disable=too-many-arguments,too-many-locals + """ + Get the result of an execution + """ job_status_url = 'Network/ibm-q/Groups/open/Projects/main/Jobs/' + execution_id @@ -229,7 +237,7 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover # STEP5: WAIT FOR THE JOB TO BE RUN json_step5 = {'allow_redirects': True, 'timeout': (self.timeout, None)} - request = super(IBMQ, self).get(urljoin(_API_URL, job_status_url), **json_step5) + request = super().get(urljoin(_API_URL, job_status_url), **json_step5) request.raise_for_status() r_json = request.json() acceptable_status = ['VALIDATING', 'VALIDATED', 'RUNNING'] @@ -239,7 +247,7 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover 'allow_redirects': True, 'timeout': (self.timeout, None), } - request = super(IBMQ, self).get( + request = super().get( urljoin(_API_URL, job_status_url + '/resultDownloadUrl'), **json_step6, ) @@ -252,13 +260,13 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover 'params': {'access_token': None}, 'timeout': (self.timeout, None), } - request = super(IBMQ, self).get(r_json['url'], **json_step7) + request = super().get(r_json['url'], **json_step7) r_json = request.json() result = r_json['results'][0] # STEP8: Confirm the data was downloaded json_step8 = {'data': None, 'json': None, 'timeout': (5.0, None)} - request = super(IBMQ, self).post( + request = super().post( urljoin(_API_URL, job_status_url + '/resultDownloaded'), **json_step8, ) @@ -285,11 +293,11 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover class DeviceTooSmall(Exception): - pass + """Exception raised if the device is too small to run the circuit""" class DeviceOfflineError(Exception): - pass + """Exception raised if a selected device is currently offline""" def show_devices(token=None, verbose=False): @@ -305,11 +313,11 @@ def show_devices(token=None, verbose=False): (list) list of available devices and their properties """ ibmq_session = IBMQ() - ibmq_session._authenticate(token=token) + 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): # pylint: disable=too-many-arguments """ Retrieves a previously run job by its ID. @@ -322,9 +330,9 @@ def retrieve(device, token, jobid, num_retries=3000, interval=1, verbose=False): (dict) result form the IBMQ server """ ibmq_session = IBMQ() - ibmq_session._authenticate(token) + 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 @@ -336,7 +344,7 @@ def send( num_retries=3000, interval=1, verbose=False, -): +): # pylint: disable=too-many-arguments """ Sends QASM through the IBM API and runs the quantum circuit. @@ -362,7 +370,7 @@ def send( print("- Authenticating...") if token is not None: print('user API token: ' + token) - ibmq_session._authenticate(token) + ibmq_session.authenticate(token) # check if the device is online ibmq_session.get_list_devices(verbose) @@ -384,10 +392,10 @@ def send( raise DeviceTooSmall("Device is too small.") if verbose: print("- Running code: {}".format(info)) - execution_id = ibmq_session._run(info, device) + execution_id = ibmq_session.run(info, device) if verbose: print("- Waiting for results...") - res = ibmq_session._get_result( + res = ibmq_session.get_result( device, execution_id, num_retries=num_retries, @@ -406,3 +414,4 @@ def send( except KeyError as err: print("- Failed to parse response:") print(err) + return None diff --git a/projectq/backends/_ibm/_ibm_test.py b/projectq/backends/_ibm/_ibm_test.py index 204878276..c16dbe461 100755 --- a/projectq/backends/_ibm/_ibm_test.py +++ b/projectq/backends/_ibm/_ibm_test.py @@ -18,6 +18,7 @@ import math from projectq.backends._ibm import _ibm from projectq.cengines import MainEngine, BasicMapperEngine, DummyEngine +from projectq.meta import LogicalQubitIDTag from projectq.ops import ( All, Allocate, @@ -382,3 +383,22 @@ def mock_send(*args, **kwargs): with pytest.raises(RuntimeError): eng.backend.get_probabilities(eng.allocate_qubit()) + + +def test_ibm_errors(): + backend = _ibm.IBMBackend(verbose=True, num_runs=1000) + mapper = BasicMapperEngine() + mapper.current_mapping = {0: 0} + eng = MainEngine(backend=backend, engine_list=[mapper]) + + qb0 = WeakQubitRef(engine=None, idx=0) + + # No LogicalQubitIDTag + with pytest.raises(RuntimeError): + eng.backend._store(Command(engine=eng, gate=Measure, qubits=([qb0],))) + + eng = MainEngine(backend=backend, engine_list=[]) + + # No mapper + with pytest.raises(RuntimeError): + eng.backend._store(Command(engine=eng, gate=Measure, qubits=([qb0],), tags=(LogicalQubitIDTag(1),))) diff --git a/projectq/backends/_ionq/__init__.py b/projectq/backends/_ionq/__init__.py index 5269e8f23..dfc37dc08 100644 --- a/projectq/backends/_ionq/__init__.py +++ b/projectq/backends/_ionq/__init__.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ProjectQ module for supporting the IonQ platform""" + from ._ionq import IonQBackend __all__ = ['IonQBackend'] diff --git a/projectq/backends/_ionq/_ionq.py b/projectq/backends/_ionq/_ionq.py index 6dfd52d20..191d3bd9b 100644 --- a/projectq/backends/_ionq/_ionq.py +++ b/projectq/backends/_ionq/_ionq.py @@ -81,7 +81,7 @@ def _rearrange_result(input_result, length): return ''.join(bin_input)[::-1] -class IonQBackend(BasicEngine): +class IonQBackend(BasicEngine): # pylint: disable=too-many-instance-attributes """Backend for building circuits and submitting them to the IonQ API.""" def __init__( @@ -94,7 +94,7 @@ def __init__( num_retries=3000, interval=1, retrieve_execution=None, - ): + ): # pylint: disable=too-many-arguments """Constructor for the IonQBackend. Args: @@ -196,7 +196,6 @@ def _store(self, cmd): # Create a measurement. if gate == Measure: - assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 logical_id = cmd.qubits[0][0].id for tag in cmd.tags: if isinstance(tag, LogicalQubitIDTag): @@ -211,7 +210,7 @@ def _store(self, cmd): gate_name = GATE_MAP.get(gate_type) # Daggered gates get special treatment. if isinstance(gate, DaggeredGate): - gate_name = GATE_MAP[type(gate._gate)] + 'i' + gate_name = GATE_MAP[type(gate._gate)] + 'i' # pylint: disable=protected-access # Unable to determine a gate mapping here, so raise out. if gate_name is None: @@ -301,7 +300,7 @@ def get_probabilities(self, qureg): probability_dict[mapped_state] = probability_dict.get(mapped_state, 0) + probability return probability_dict - def _run(self): + def _run(self): # pylint: disable=too-many-locals """Run the circuit this object has built during engine execution.""" # Nothing to do with an empty circuit. if len(self._circuit) == 0: @@ -341,7 +340,7 @@ def _run(self): self._measured_ids = measured_ids = res['meas_qubit_ids'] # Determine random outcome from probable states. - P = random.random() + random_outcome = random.random() p_sum = 0.0 measured = "" star = "" @@ -353,7 +352,7 @@ def _run(self): state = _rearrange_result(int(state_int), num_measured) probability = probable_outcomes[state_int] p_sum += probability - if p_sum >= P and measured == "" or (idx == len(states) - 1): + if p_sum >= random_outcome and measured == "" or (idx == len(states) - 1): measured = state star = "*" self._probabilities[state] = probability diff --git a/projectq/backends/_ionq/_ionq_http_client.py b/projectq/backends/_ionq/_ionq_http_client.py index 8bcab285f..8f45389b9 100644 --- a/projectq/backends/_ionq/_ionq_http_client.py +++ b/projectq/backends/_ionq/_ionq_http_client.py @@ -38,7 +38,7 @@ class IonQ(Session): """A requests.Session based HTTP client for the IonQ API.""" def __init__(self, verbose=False): - super(IonQ, self).__init__() + super().__init__() self.backends = dict() self.timeout = 5.0 self.token = None @@ -93,7 +93,7 @@ def can_run_experiment(self, info, device): 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): """Set an Authorization header for this session. If no token is provided, an prompt will appear to ask for one. @@ -108,7 +108,7 @@ def _authenticate(self, token=None): self.headers.update({'Authorization': 'apiKey {}'.format(token)}) self.token = token - def _run(self, info, device): + def run(self, info, device): """Run a circuit from ``info`` on the specified ``device``. Args: @@ -140,7 +140,7 @@ def _run(self, info, device): # _API_URL[:-1] strips the trailing slash. # TODO: Add comprehensive error parsing for non-200 responses. - req = super(IonQ, self).post(_API_URL[:-1], json=argument) + req = super().post(_API_URL[:-1], json=argument) req.raise_for_status() # Process the response. @@ -164,7 +164,7 @@ def _run(self, info, device): ) ) - def _get_result(self, device, execution_id, num_retries=3000, interval=1): + def get_result(self, device, execution_id, num_retries=3000, interval=1): """Given a backend and ID, fetch the results for this job's execution. The return dictionary should have at least: @@ -203,7 +203,7 @@ def _handle_sigint_during_get_result(*_): # pragma: no cover try: for retries in range(num_retries): - req = super(IonQ, self).get(urljoin(_API_URL, execution_id)) + req = super().get(urljoin(_API_URL, execution_id)) req.raise_for_status() r_json = req.json() status = r_json['status'] @@ -261,7 +261,7 @@ def retrieve( num_retries=3000, interval=1, verbose=False, -): +): # pylint: disable=too-many-arguments """Retrieve an already submitted IonQ job. Args: @@ -279,9 +279,9 @@ def retrieve( dict: A dict with job submission results. """ ionq_session = IonQ(verbose=verbose) - ionq_session._authenticate(token) + ionq_session.authenticate(token) ionq_session.update_devices_list() - res = ionq_session._get_result( + res = ionq_session.get_result( device, jobid, num_retries=num_retries, @@ -297,7 +297,7 @@ def send( num_retries=100, interval=1, verbose=False, -): +): # pylint: disable=too-many-arguments,too-many-locals """Submit a job to the IonQ API. The ``info`` dict should have at least the following keys:: @@ -334,7 +334,7 @@ def send( print("- Authenticating...") if verbose and token is not None: # pragma: no cover print('user API token: ' + token) - ionq_session._authenticate(token) + ionq_session.authenticate(token) # check if the device is online ionq_session.update_devices_list() @@ -356,10 +356,10 @@ def send( raise DeviceTooSmall("Device is too small.") if verbose: # pragma: no cover print("- Running code: {}".format(info)) - execution_id = ionq_session._run(info, device) + execution_id = ionq_session.run(info, device) if verbose: # pragma: no cover print("- Waiting for results...") - res = ionq_session._get_result( + res = ionq_session.get_result( device, execution_id, num_retries=num_retries, @@ -383,7 +383,7 @@ def send( err_json['error'], err_json['message'], ) - ) + ) from err # Else, just print: print("- There was an error running your code:") @@ -391,6 +391,7 @@ def send( except requests.exceptions.RequestException as err: print("- Looks like something is wrong with server:") print(err) + return None __all__ = [ diff --git a/projectq/backends/_ionq/_ionq_http_client_test.py b/projectq/backends/_ionq/_ionq_http_client_test.py index 7b09a00f8..c1586569d 100644 --- a/projectq/backends/_ionq/_ionq_http_client_test.py +++ b/projectq/backends/_ionq/_ionq_http_client_test.py @@ -35,7 +35,7 @@ def no_requests(monkeypatch): def test_authenticate(): ionq_session = _ionq_http_client.IonQ() - ionq_session._authenticate('NotNone') + ionq_session.authenticate('NotNone') assert 'Authorization' in ionq_session.headers assert ionq_session.token == 'NotNone' assert ionq_session.headers['Authorization'] == 'apiKey NotNone' @@ -49,13 +49,13 @@ def user_password_input(prompt): monkeypatch.setattr('getpass.getpass', user_password_input) ionq_session = _ionq_http_client.IonQ() with pytest.raises(RuntimeError) as excinfo: - ionq_session._authenticate() + ionq_session.authenticate() assert str(excinfo.value) == 'An authentication token is required!' def test_is_online(): ionq_session = _ionq_http_client.IonQ() - ionq_session._authenticate('not none') + ionq_session.authenticate('not none') ionq_session.update_devices_list() assert ionq_session.is_online('ionq_simulator') assert ionq_session.is_online('ionq_qpu') diff --git a/projectq/backends/_ionq/_ionq_mapper.py b/projectq/backends/_ionq/_ionq_mapper.py index 9b7450b0d..24c330f0f 100644 --- a/projectq/backends/_ionq/_ionq_mapper.py +++ b/projectq/backends/_ionq/_ionq_mapper.py @@ -20,6 +20,8 @@ class BoundedQubitMapper(BasicMapperEngine): + """Maps logical qubits to a fixed number of hardware qubits""" + def __init__(self, max_qubits): super().__init__() self._qubit_idx = 0 diff --git a/projectq/backends/_printer.py b/projectq/backends/_printer.py index 69223b706..fc91ae839 100755 --- a/projectq/backends/_printer.py +++ b/projectq/backends/_printer.py @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Contains a compiler engine which prints commands to stdout prior to sending -them on to the next engines (see CommandPrinter). +Contains a compiler engine which prints commands to stdout prior to sending them on to the next engines (see +CommandPrinter). """ import sys @@ -28,8 +28,8 @@ class CommandPrinter(BasicEngine): """ - CommandPrinter is a compiler engine which prints commands to stdout prior - to sending them on to the next compiler engine. + CommandPrinter is a compiler engine which prints commands to stdout prior to sending them on to the next compiler + engine. """ def __init__(self, accept_input=True, default_measure=False, in_place=False): @@ -37,14 +37,10 @@ def __init__(self, accept_input=True, default_measure=False, in_place=False): Initialize a CommandPrinter. Args: - accept_input (bool): If accept_input is true, the printer queries - the user to input measurement results if the CommandPrinter is - the last engine. Otherwise, all measurements yield - default_measure. - default_measure (bool): Default measurement result (if - accept_input is False). - in_place (bool): If in_place is true, all output is written on the - same line of the terminal. + accept_input (bool): If accept_input is true, the printer queries the user to input measurement results if + the CommandPrinter is the last engine. Otherwise, all measurements yield default_measure. + default_measure (bool): Default measurement result (if accept_input is False). + in_place (bool): If in_place is true, all output is written on the same line of the terminal. """ BasicEngine.__init__(self) self._accept_input = accept_input @@ -53,15 +49,13 @@ def __init__(self, accept_input=True, default_measure=False, in_place=False): def is_available(self, cmd): """ - Specialized implementation of is_available: Returns True if the - CommandPrinter is the last engine (since it can print any command). + Specialized implementation of is_available: Returns True if the CommandPrinter is the last engine (since it + can print any command). Args: - cmd (Command): Command of which to check availability (all - Commands can be printed). + cmd (Command): Command of which to check availability (all Commands can be printed). Returns: - availability (bool): True, unless the next engine cannot handle - the Command (if there is a next engine). + availability (bool): True, unless the next engine cannot handle the Command (if there is a next engine). """ try: return BasicEngine.is_available(self, cmd) @@ -70,27 +64,28 @@ def is_available(self, cmd): def _print_cmd(self, cmd): """ - Print a command or, if the command is a measurement instruction and - the CommandPrinter is the last engine in the engine pipeline: Query - the user for the measurement result (if accept_input = True) / Set - the result to 0 (if it's False). + Print a command or, if the command is a measurement instruction and the CommandPrinter is the last engine in + the engine pipeline: Query the user for the measurement result (if accept_input = True) / Set the result to 0 + (if it's False). Args: cmd (Command): Command to print. """ if self.is_last_engine and cmd.gate == Measure: - assert get_control_count(cmd) == 0 + if get_control_count(cmd) != 0: + raise ValueError('Cannot have control qubits with a measurement gate!') + print(cmd) for qureg in cmd.qubits: for qubit in qureg: if self._accept_input: - m = None - while m != '0' and m != '1' and m != 1 and m != 0: + meas = None + while meas not in ('0', '1', 1, 0): prompt = "Input measurement result (0 or 1) for qubit " + str(qubit) + ": " - m = input(prompt) + meas = input(prompt) else: - m = self._default_measure - m = int(m) + meas = self._default_measure + meas = int(meas) # Check there was a mapper and redirect result logical_id_tag = None for tag in cmd.tags: @@ -98,7 +93,7 @@ def _print_cmd(self, cmd): logical_id_tag = tag if logical_id_tag is not None: qubit = WeakQubitRef(qubit.engine, logical_id_tag.logical_qubit_id) - self.main_engine.set_measurement_result(qubit, m) + self.main_engine.set_measurement_result(qubit, meas) else: if self._in_place: # pragma: no cover sys.stdout.write("\0\r\t\x1b[K" + str(cmd) + "\r") diff --git a/projectq/backends/_printer_test.py b/projectq/backends/_printer_test.py index 872423e26..3c06b3ed1 100755 --- a/projectq/backends/_printer_test.py +++ b/projectq/backends/_printer_test.py @@ -59,6 +59,16 @@ def test_command_printer_accept_input(monkeypatch): assert int(qubit) == 0 +def test_command_printer_measure_no_control(): + qb1 = WeakQubitRef(engine=None, idx=1) + qb2 = WeakQubitRef(engine=None, idx=2) + + printer = _printer.CommandPrinter() + printer.is_last_engine = True + with pytest.raises(ValueError): + printer._print_cmd(Command(engine=None, gate=Measure, qubits=([qb1],), controls=[qb2])) + + def test_command_printer_no_input_default_measure(): cmd_printer = _printer.CommandPrinter(accept_input=False) eng = MainEngine(backend=cmd_printer, engine_list=[DummyEngine()]) diff --git a/projectq/backends/_resource.py b/projectq/backends/_resource.py index 97317778f..558b298bf 100755 --- a/projectq/backends/_resource.py +++ b/projectq/backends/_resource.py @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Contains a compiler engine which counts the number of calls for each type of -gate used in a circuit, in addition to the max. number of active qubits. +Contains a compiler engine which counts the number of calls for each type of gate used in a circuit, in addition to +the max. number of active qubits. """ from projectq.cengines import BasicEngine, LastEngineException @@ -25,21 +25,16 @@ class ResourceCounter(BasicEngine): """ - ResourceCounter is a compiler engine which counts the number of gates and - max. number of active qubits. + ResourceCounter is a compiler engine which counts the number of gates and max. number of active qubits. Attributes: - gate_counts (dict): Dictionary of gate counts. - The keys are tuples of the form (cmd.gate, ctrl_cnt), where + gate_counts (dict): Dictionary of gate counts. The keys are tuples of the form (cmd.gate, ctrl_cnt), where ctrl_cnt is the number of control qubits. - gate_class_counts (dict): Dictionary of gate class counts. - The keys are tuples of the form (cmd.gate.__class__, ctrl_cnt), - where ctrl_cnt is the number of control qubits. - max_width (int): Maximal width (=max. number of active qubits at any - given point). + gate_class_counts (dict): Dictionary of gate class counts. The keys are tuples of the form + (cmd.gate.__class__, ctrl_cnt), where ctrl_cnt is the number of control qubits. + max_width (int): Maximal width (=max. number of active qubits at any given point). Properties: - depth_of_dag (int): It is the longest path in the directed - acyclic graph (DAG) of the program. + depth_of_dag (int): It is the longest path in the directed acyclic graph (DAG) of the program. """ def __init__(self): @@ -59,16 +54,14 @@ def __init__(self): def is_available(self, cmd): """ - Specialized implementation of is_available: Returns True if the - ResourceCounter is the last engine (since it can count any command). + Specialized implementation of is_available: Returns True if the ResourceCounter is the last engine (since it + can count any command). Args: - cmd (Command): Command for which to check availability (all - Commands can be counted). + cmd (Command): Command for which to check availability (all Commands can be counted). Returns: - availability (bool): True, unless the next engine cannot handle - the Command (if there is a next engine). + availability (bool): True, unless the next engine cannot handle the Command (if there is a next engine). """ try: return BasicEngine.is_available(self, cmd) @@ -77,13 +70,15 @@ def is_available(self, cmd): @property def depth_of_dag(self): + """ + Return the depth of the DAG. + """ if self._depth_of_qubit: current_max = max(self._depth_of_qubit.values()) return max(current_max, self._previous_max_depth) - else: - return self._previous_max_depth + return self._previous_max_depth - def _add_cmd(self, cmd): + def _add_cmd(self, cmd): # pylint: disable=too-many-branches """ Add a gate to the count. """ @@ -142,9 +137,8 @@ def __str__(self): Return the string representation of this ResourceCounter. Returns: - A summary (string) of resources used, including gates, number of - calls, and max. number of qubits that were active at the same - time. + A summary (string) of resources used, including gates, number of calls, and max. number of qubits that + were active at the same time. """ if len(self.gate_counts) > 0: gate_class_list = [] @@ -172,13 +166,11 @@ def __str__(self): def receive(self, command_list): """ - Receive a list of commands from the previous engine, increases the - counters of the received commands, and then send them on to the next - engine. + Receive a list of commands from the previous engine, increases the counters of the received commands, and then + send them on to the next engine. Args: - command_list (list): List of commands to receive (and - count). + command_list (list): List of commands to receive (and count). """ for cmd in command_list: if not cmd.gate == FlushGate(): diff --git a/projectq/backends/_sim/__init__.py b/projectq/backends/_sim/__init__.py index 513e6d4b0..c0d0d5d3f 100755 --- a/projectq/backends/_sim/__init__.py +++ b/projectq/backends/_sim/__init__.py @@ -13,5 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ProjectQ module dedicated to simulation""" + from ._simulator import Simulator from ._classical_simulator import ClassicalSimulator diff --git a/projectq/backends/_sim/_classical_simulator.py b/projectq/backends/_sim/_classical_simulator.py index 3d93c1467..308987ca8 100755 --- a/projectq/backends/_sim/_classical_simulator.py +++ b/projectq/backends/_sim/_classical_simulator.py @@ -26,9 +26,8 @@ class ClassicalSimulator(BasicEngine): """ A simple introspective simulator that only permits classical operations. - Allows allocation, deallocation, measuring (no-op), flushing (no-op), - controls, NOTs, and any BasicMathGate. Supports reading/writing directly - from/to bits and registers of bits. + Allows allocation, deallocation, measuring (no-op), flushing (no-op), controls, NOTs, and any + BasicMathGate. Supports reading/writing directly from/to bits and registers of bits. """ def __init__(self): @@ -48,17 +47,15 @@ def _convert_logical_to_mapped_qubit(self, qubit): if qubit.id not in mapper.current_mapping: raise RuntimeError("Unknown qubit id. Please make sure you have called eng.flush().") return WeakQubitRef(qubit.engine, mapper.current_mapping[qubit.id]) - else: - return qubit + return qubit def read_bit(self, qubit): """ Reads a bit. Note: - If there is a mapper present in the compiler, this function - automatically converts from logical qubits to mapped qubits for - the qureg argument. + If there is a mapper present in the compiler, this function automatically converts from logical qubits to + mapped qubits for the qureg argument. Args: qubit (projectq.types.Qubit): The bit to read. @@ -71,17 +68,15 @@ def read_bit(self, qubit): def _read_mapped_bit(self, mapped_qubit): """Internal use only. Does not change logical to mapped qubits.""" - p = self._bit_positions[mapped_qubit.id] - return (self._state >> p) & 1 + return (self._state >> self._bit_positions[mapped_qubit.id]) & 1 def write_bit(self, qubit, value): """ Resets/sets a bit to the given value. Note: - If there is a mapper present in the compiler, this function - automatically converts from logical qubits to mapped qubits for - the qureg argument. + If there is a mapper present in the compiler, this function automatically converts from logical qubits to + mapped qubits for the qureg argument. Args: qubit (projectq.types.Qubit): The bit to write. @@ -92,37 +87,34 @@ def write_bit(self, qubit, value): def _write_mapped_bit(self, mapped_qubit, value): """Internal use only. Does not change logical to mapped qubits.""" - p = self._bit_positions[mapped_qubit.id] + pos = self._bit_positions[mapped_qubit.id] if value: - self._state |= 1 << p + self._state |= 1 << pos else: - self._state &= ~(1 << p) + self._state &= ~(1 << pos) def _mask(self, qureg): """ - Returns a mask, to compare against the state, with bits from the - register set to 1 and other bits set to 0. + Returns a mask, to compare against the state, with bits from the register set to 1 and other bits set to 0. Args: - qureg (projectq.types.Qureg): - The bits whose positions should be set. + qureg (projectq.types.Qureg): The bits whose positions should be set. Returns: int: The mask. """ - t = 0 - for q in qureg: - t |= 1 << self._bit_positions[q.id] - return t + mask = 0 + for qb in qureg: + mask |= 1 << self._bit_positions[qb.id] + return mask def read_register(self, qureg): """ Reads a group of bits as a little-endian integer. Note: - If there is a mapper present in the compiler, this function - automatically converts from logical qubits to mapped qubits for - the qureg argument. + If there is a mapper present in the compiler, this function automatically converts from logical qubits to + mapped qubits for the qureg argument. Args: qureg (projectq.types.Qureg): @@ -138,23 +130,21 @@ def read_register(self, qureg): def _read_mapped_register(self, mapped_qureg): """Internal use only. Does not change logical to mapped qubits.""" - t = 0 - for i in range(len(mapped_qureg)): - t |= self._read_mapped_bit(mapped_qureg[i]) << i - return t + mask = 0 + for i, qubit in enumerate(mapped_qureg): + mask |= self._read_mapped_bit(qubit) << i + return mask def write_register(self, qureg, value): """ Sets a group of bits to store a little-endian integer value. Note: - If there is a mapper present in the compiler, this function - automatically converts from logical qubits to mapped qubits for - the qureg argument. + If there is a mapper present in the compiler, this function automatically converts from logical qubits to + mapped qubits for the qureg argument. Args: - qureg (projectq.types.Qureg): - The bits to write, in little-endian order. + qureg (projectq.types.Qureg): The bits to write, in little-endian order. value (int): The integer value to store. Must fit in the register. """ new_qureg = [] @@ -166,41 +156,40 @@ def _write_mapped_register(self, mapped_qureg, value): """Internal use only. Does not change logical to mapped qubits.""" if value < 0 or value >= 1 << len(mapped_qureg): raise ValueError("Value won't fit in register.") - for i in range(len(mapped_qureg)): - self._write_mapped_bit(mapped_qureg[i], (value >> i) & 1) + for i, mapped_qubit in enumerate(mapped_qureg): + self._write_mapped_bit(mapped_qubit, (value >> i) & 1) def is_available(self, cmd): return ( cmd.gate == Measure or cmd.gate == Allocate or cmd.gate == Deallocate - or isinstance(cmd.gate, BasicMathGate) - or isinstance(cmd.gate, FlushGate) - or isinstance(cmd.gate, XGate) + or isinstance(cmd.gate, (BasicMathGate, FlushGate, XGate)) ) def receive(self, command_list): + """Forward all commands to the next engine.""" for cmd in command_list: self._handle(cmd) if not self.is_last_engine: self.send(command_list) - def _handle(self, cmd): + def _handle(self, cmd): # pylint: disable=too-many-branches,too-many-locals if isinstance(cmd.gate, FlushGate): return if cmd.gate == Measure: - for qr in cmd.qubits: - for qb in qr: + for qureg in cmd.qubits: + for qubit in qureg: # Check if a mapper assigned a different logical id logical_id_tag = None for tag in cmd.tags: if isinstance(tag, LogicalQubitIDTag): logical_id_tag = tag - log_qb = qb + log_qb = qubit if logical_id_tag is not None: - log_qb = WeakQubitRef(qb.engine, logical_id_tag.logical_qubit_id) - self.main_engine.set_measurement_result(log_qb, self._read_mapped_bit(qb)) + log_qb = WeakQubitRef(qubit.engine, logical_id_tag.logical_qubit_id) + self.main_engine.set_measurement_result(log_qb, self._read_mapped_bit(qubit)) return if cmd.gate == Allocate: @@ -221,7 +210,8 @@ def _handle(self, cmd): meets_controls = self._state & controls_mask == controls_mask if isinstance(cmd.gate, XGate): - assert len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1 + if not (len(cmd.qubits) == 1 and len(cmd.qubits[0]) == 1): + raise ValueError('The XGate only accepts one qubit!') target = cmd.qubits[0][0] if meets_controls: self._write_mapped_bit(target, not self._read_mapped_bit(target)) diff --git a/projectq/backends/_sim/_classical_simulator_test.py b/projectq/backends/_sim/_classical_simulator_test.py index f9c33dc90..8a35d2159 100755 --- a/projectq/backends/_sim/_classical_simulator_test.py +++ b/projectq/backends/_sim/_classical_simulator_test.py @@ -164,6 +164,15 @@ def test_write_register_value_error_exception(mapper): # noqa: F811 sim.write_register(a, 8) +def test_x_gate_invalid(): + sim = ClassicalSimulator() + eng = MainEngine(sim, [AutoReplacer(DecompositionRuleSet())]) + a = eng.allocate_qureg(2) + + with pytest.raises(ValueError): + X | a + + def test_available_gates(): sim = ClassicalSimulator() eng = MainEngine(sim, [AutoReplacer(DecompositionRuleSet())]) diff --git a/projectq/backends/_sim/_cppkernels/simulator.hpp b/projectq/backends/_sim/_cppkernels/simulator.hpp index 19e4b173c..1a84723f7 100755 --- a/projectq/backends/_sim/_cppkernels/simulator.hpp +++ b/projectq/backends/_sim/_cppkernels/simulator.hpp @@ -455,7 +455,8 @@ class Simulator{ void collapse_wavefunction(std::vector const& ids, std::vector const& values){ run(); - assert(ids.size() == values.size()); + if (ids.size() != values.size()) + throw(std::length_error("collapse_wavefunction(): ids and values size mismatch")); if (!check_ids(ids)) throw(std::runtime_error("collapse_wavefunction(): Unknown qubit id(s) provided. Try calling eng.flush() before invoking this function.")); std::size_t mask = 0, val = 0; diff --git a/projectq/backends/_sim/_pysim.py b/projectq/backends/_sim/_pysim.py index 3900b4b7b..54860cafd 100755 --- a/projectq/backends/_sim/_pysim.py +++ b/projectq/backends/_sim/_pysim.py @@ -19,31 +19,29 @@ """ import random -import numpy as _np import os +import numpy as _np _USE_REFCHECK = True if 'TRAVIS' in os.environ: # pragma: no cover _USE_REFCHECK = False -class Simulator(object): +class Simulator: """ Python implementation of a quantum computer simulator. - This Simulator can be used as a backup if compiling the c++ simulator is - not an option (for some reason). It has the same features but is much - slower, so please consider building the c++ version for larger experiments. + This Simulator can be used as a backup if compiling the c++ simulator is not an option (for some reason). It has the + same features but is much slower, so please consider building the c++ version for larger experiments. """ - def __init__(self, rnd_seed, *args, **kwargs): + def __init__(self, rnd_seed, *args, **kwargs): # pylint: disable=unused-argument """ Initialize the simulator. Args: rnd_seed (int): Seed to initialize the random number generator. - args: Dummy argument to allow an interface identical to the c++ - simulator. + args: Dummy argument to allow an interface identical to the c++ simulator. kwargs: Same as args. """ random.seed(rnd_seed) @@ -54,16 +52,13 @@ def __init__(self, rnd_seed, *args, **kwargs): def cheat(self): """ - Return the qubit index to bit location map and the corresponding state - vector. + Return the qubit index to bit location map and the corresponding state vector. - This function can be used to measure expectation values more - efficiently (emulation). + This function can be used to measure expectation values more efficiently (emulation). Returns: - A tuple where the first entry is a dictionary mapping qubit indices - to bit-locations and the second entry is the corresponding state - vector + A tuple where the first entry is a dictionary mapping qubit indices to bit-locations and the second entry is + the corresponding state vector """ return (self._map, self._state) @@ -78,10 +73,10 @@ def measure_qubits(self, ids): Returns: List of measurement results (containing either True or False). """ - P = random.random() + random_outcome = random.random() val = 0.0 i_picked = 0 - while val < P and i_picked < len(self._state): + while val < random_outcome and i_picked < len(self._state): val += _np.abs(self._state[i_picked]) ** 2 i_picked += 1 @@ -92,82 +87,78 @@ def measure_qubits(self, ids): mask = 0 val = 0 - for i in range(len(pos)): - res[i] = ((i_picked >> pos[i]) & 1) == 1 - mask |= 1 << pos[i] - val |= (res[i] & 1) << pos[i] + for i, _pos in enumerate(pos): + res[i] = ((i_picked >> _pos) & 1) == 1 + mask |= 1 << _pos + val |= (res[i] & 1) << _pos nrm = 0.0 - for i in range(len(self._state)): + for i, _state in enumerate(self._state): if (mask & i) != val: self._state[i] = 0.0 else: - nrm += _np.abs(self._state[i]) ** 2 + nrm += _np.abs(_state) ** 2 self._state *= 1.0 / _np.sqrt(nrm) return res - def allocate_qubit(self, ID): + def allocate_qubit(self, qubit_id): """ Allocate a qubit. Args: - ID (int): ID of the qubit which is being allocated. + qubit_id (int): ID of the qubit which is being allocated. """ - self._map[ID] = self._num_qubits + self._map[qubit_id] = self._num_qubits self._num_qubits += 1 self._state.resize(1 << self._num_qubits, refcheck=_USE_REFCHECK) - def get_classical_value(self, ID, tol=1.0e-10): + def get_classical_value(self, qubit_id, tol=1.0e-10): """ - Return the classical value of a classical bit (i.e., a qubit which has - been measured / uncomputed). + Return the classical value of a classical bit (i.e., a qubit which has been measured / uncomputed). Args: - ID (int): ID of the qubit of which to get the classical value. - tol (float): Tolerance for numerical errors when determining - whether the qubit is indeed classical. + qubit_it (int): ID of the qubit of which to get the classical value. + tol (float): Tolerance for numerical errors when determining whether the qubit is indeed classical. Raises: - RuntimeError: If the qubit is in a superposition, i.e., has not - been measured / uncomputed. + RuntimeError: If the qubit is in a superposition, i.e., has not been measured / uncomputed. """ - pos = self._map[ID] - up = down = False + pos = self._map[qubit_id] + state_up = state_down = False for i in range(0, len(self._state), (1 << (pos + 1))): for j in range(0, (1 << pos)): if _np.abs(self._state[i + j]) > tol: - up = True + state_up = True if _np.abs(self._state[i + j + (1 << pos)]) > tol: - down = True - if up and down: + state_down = True + if state_up and state_down: raise RuntimeError( "Qubit has not been measured / " "uncomputed. Cannot access its " "classical value and/or deallocate a " "qubit in superposition!" ) - return down + return state_down - def deallocate_qubit(self, ID): + def deallocate_qubit(self, qubit_id): """ Deallocate a qubit (if it has been measured / uncomputed). Args: - ID (int): ID of the qubit to deallocate. + qubit_id (int): ID of the qubit to deallocate. Raises: - RuntimeError: If the qubit is in a superposition, i.e., has not - been measured / uncomputed. + RuntimeError: If the qubit is in a superposition, i.e., has not been measured / uncomputed. """ - pos = self._map[ID] + pos = self._map[qubit_id] - cv = self.get_classical_value(ID) + classical_value = self.get_classical_value(qubit_id) newstate = _np.zeros((1 << (self._num_qubits - 1)), dtype=_np.complex128) k = 0 - for i in range((1 << pos) * int(cv), len(self._state), (1 << (pos + 1))): + for i in range((1 << pos) * int(classical_value), len(self._state), (1 << (pos + 1))): newstate[k : k + (1 << pos)] = self._state[i : i + (1 << pos)] # noqa: E203 k += 1 << pos @@ -175,7 +166,7 @@ def deallocate_qubit(self, ID): for key, value in self._map.items(): if value > pos: newmap[key] = value - 1 - elif key != ID: + elif key != qubit_id: newmap[key] = value self._map = newmap self._state = newstate @@ -194,15 +185,14 @@ def _get_control_mask(self, ctrlids): mask |= 1 << ctrlpos return mask - def emulate_math(self, f, qubit_ids, ctrlqubit_ids): + def emulate_math(self, func, qubit_ids, ctrlqubit_ids): # pylint: disable=too-many-locals """ Emulate a math function (e.g., BasicMathGate). Args: - f (function): Function executing the operation to emulate. - qubit_ids (list>): List of lists of qubit IDs to which - the gate is being applied. Every gate is applied to a tuple of - quantum registers, which corresponds to this 'list of lists'. + func (function): Function executing the operation to emulate. + qubit_ids (list>): List of lists of qubit IDs to which the gate is being applied. Every gate is + applied to a tuple of quantum registers, which corresponds to this 'list of lists'. ctrlqubit_ids (list): List of control qubit ids. """ mask = self._get_control_mask(ctrlqubit_ids) @@ -217,16 +207,16 @@ def emulate_math(self, f, qubit_ids, ctrlqubit_ids): for i in range(0, len(self._state)): if (mask & i) == mask: arg_list = [0] * len(qb_locs) - for qr_i in range(len(qb_locs)): - for qb_i in range(len(qb_locs[qr_i])): - arg_list[qr_i] |= ((i >> qb_locs[qr_i][qb_i]) & 1) << qb_i + for qr_i, qr_loc in enumerate(qb_locs): + for qb_i, qb_loc in enumerate(qr_loc): + arg_list[qr_i] |= ((i >> qb_loc) & 1) << qb_i - res = f(arg_list) + res = func(arg_list) new_i = i - for qr_i in range(len(qb_locs)): - for qb_i in range(len(qb_locs[qr_i])): - if not (((new_i >> qb_locs[qr_i][qb_i]) & 1) == ((res[qr_i] >> qb_i) & 1)): - new_i ^= 1 << qb_locs[qr_i][qb_i] + for qr_i, qr_loc in enumerate(qb_locs): + for qb_i, qb_loc in enumerate(qr_loc): + if not ((new_i >> qb_loc) & 1) == ((res[qr_i] >> qb_i) & 1): + new_i ^= 1 << qb_loc newstate[new_i] = self._state[i] else: newstate[i] = self._state[i] @@ -272,8 +262,7 @@ def apply_qubit_operator(self, terms_dict, ids): def get_probability(self, bit_string, ids): """ - Return the probability of the outcome `bit_string` when measuring - the qubits given by the list of ids. + Return the probability of the outcome `bit_string` when measuring the qubits given by the list of ids. Args: bit_string (list[bool|int]): Measurement outcome. @@ -285,135 +274,119 @@ def get_probability(self, bit_string, ids): Raises: RuntimeError if an unknown qubit id was provided. """ - for i in range(len(ids)): - if ids[i] not in self._map: + for qubit_id in ids: + if qubit_id not in self._map: raise RuntimeError("get_probability(): Unknown qubit id. Please make sure you have called eng.flush().") mask = 0 bit_str = 0 - for i in range(len(ids)): - mask |= 1 << self._map[ids[i]] - bit_str |= bit_string[i] << self._map[ids[i]] + for i, qubit_id in enumerate(ids): + mask |= 1 << self._map[qubit_id] + bit_str |= bit_string[i] << self._map[qubit_id] probability = 0.0 - for i in range(len(self._state)): + for i, state in enumerate(self._state): if (i & mask) == bit_str: - e = self._state[i] - probability += e.real ** 2 + e.imag ** 2 + probability += state.real ** 2 + state.imag ** 2 return probability def get_amplitude(self, bit_string, ids): """ - Return the probability amplitude of the supplied `bit_string`. - The ordering is given by the list of qubit ids. + Return the probability amplitude of the supplied `bit_string`. The ordering is given by the list of qubit ids. Args: bit_string (list[bool|int]): Computational basis state - ids (list[int]): List of qubit ids determining the - ordering. Must contain all allocated qubits. + ids (list[int]): List of qubit ids determining the ordering. Must contain all allocated qubits. Returns: Probability amplitude of the provided bit string. Raises: - RuntimeError if the second argument is not a permutation of all - allocated qubits. + RuntimeError if the second argument is not a permutation of all allocated qubits. """ if not set(ids) == set(self._map): raise RuntimeError( - "The second argument to get_amplitude() must" - " be a permutation of all allocated qubits. " - "Please make sure you have called " - "eng.flush()." + "The second argument to get_amplitude() must be a permutation of all allocated qubits. " + "Please make sure you have called eng.flush()." ) index = 0 - for i in range(len(ids)): - index |= bit_string[i] << self._map[ids[i]] + for i, qubit_id in enumerate(ids): + index |= bit_string[i] << self._map[qubit_id] return self._state[index] - def emulate_time_evolution(self, terms_dict, time, ids, ctrlids): + def emulate_time_evolution(self, terms_dict, time, ids, ctrlids): # pylint: disable=too-many-locals """ - Applies exp(-i*time*H) to the wave function, i.e., evolves under - the Hamiltonian H for a given time. The terms in the Hamiltonian - are not required to commute. + Applies exp(-i*time*H) to the wave function, i.e., evolves under the Hamiltonian H for a given time. The terms + in the Hamiltonian are not required to commute. - This function computes the action of the matrix exponential using - ideas from Al-Mohy and Higham, 2011. + This function computes the action of the matrix exponential using ideas from Al-Mohy and Higham, 2011. TODO: Implement better estimates for s. Args: - terms_dict (dict): Operator dictionary (see QubitOperator.terms) - defining the Hamiltonian. + terms_dict (dict): Operator dictionary (see QubitOperator.terms) defining the Hamiltonian. time (scalar): Time to evolve for ids (list): A list of qubit IDs to which to apply the evolution. ctrlids (list): A list of control qubit IDs. """ - # Determine the (normalized) trace, which is nonzero only for identity - # terms: - tr = sum([c for (t, c) in terms_dict if len(t) == 0]) + # Determine the (normalized) trace, which is nonzero only for identity terms: + trace = sum([c for (t, c) in terms_dict if len(t) == 0]) terms_dict = [(t, c) for (t, c) in terms_dict if len(t) > 0] op_nrm = abs(time) * sum([abs(c) for (_, c) in terms_dict]) # rescale the operator by s: - s = int(op_nrm + 1.0) - correction = _np.exp(-1j * time * tr / float(s)) + scale = int(op_nrm + 1.0) + correction = _np.exp(-1j * time * trace / float(scale)) output_state = _np.copy(self._state) mask = self._get_control_mask(ctrlids) - for i in range(s): + for _ in range(scale): j = 0 nrm_change = 1.0 while nrm_change > 1.0e-12: - coeff = (-time * 1j) / float(s * (j + 1)) + coeff = (-time * 1j) / float(scale * (j + 1)) current_state = _np.copy(self._state) update = 0j - for t, c in terms_dict: - self._apply_term(t, ids) - self._state *= c + for term, tcoeff in terms_dict: + self._apply_term(term, ids) + self._state *= tcoeff update += self._state self._state = _np.copy(current_state) update *= coeff self._state = update - for i in range(len(update)): - if (i & mask) == mask: - output_state[i] += update[i] + for k, _update in enumerate(update): + if (k & mask) == mask: + output_state[k] += _update nrm_change = _np.linalg.norm(update) j += 1 - for i in range(len(update)): - if (i & mask) == mask: - output_state[i] *= correction + for k in range(len(update)): + if (k & mask) == mask: + output_state[k] *= correction self._state = _np.copy(output_state) - def apply_controlled_gate(self, m, ids, ctrlids): + def apply_controlled_gate(self, matrix, ids, ctrlids): """ - Applies the k-qubit gate matrix m to the qubits with indices ids, - using ctrlids as control qubits. + Applies the k-qubit gate matrix m to the qubits with indices ids, using ctrlids as control qubits. Args: - m (list[list]): 2^k x 2^k complex matrix describing the k-qubit - gate. - ids (list): A list containing the qubit IDs to which to apply the - gate. - ctrlids (list): A list of control qubit IDs (i.e., the gate is - only applied where these qubits are 1). + matrix (list[list]): 2^k x 2^k complex matrix describing the k-qubit gate. + ids (list): A list containing the qubit IDs to which to apply the gate. + ctrlids (list): A list of control qubit IDs (i.e., the gate is only applied where these qubits are 1). """ mask = self._get_control_mask(ctrlids) - if len(m) == 2: + if len(matrix) == 2: pos = self._map[ids[0]] - self._single_qubit_gate(m, pos, mask) + self._single_qubit_gate(matrix, pos, mask) else: pos = [self._map[ID] for ID in ids] - self._multi_qubit_gate(m, pos, mask) + self._multi_qubit_gate(matrix, pos, mask) - def _single_qubit_gate(self, m, pos, mask): + def _single_qubit_gate(self, matrix, pos, mask): """ - Applies the single qubit gate matrix m to the qubit at position `pos` - using `mask` to identify control qubits. + Applies the single qubit gate matrix m to the qubit at position `pos` using `mask` to identify control qubits. Args: - m (list[list]): 2x2 complex matrix describing the single-qubit - gate. + matrix (list[list]): 2x2 complex matrix describing the single-qubit gate. pos (int): Bit-position of the qubit. mask (int): Bit-mask where set bits indicate control qubits. """ - def kernel(u, d, m): + def kernel(u, d, m): # pylint: disable=invalid-name return u * m[0][0] + d * m[0][1], u * m[1][0] + d * m[1][1] for i in range(0, len(self._state), (1 << (pos + 1))): @@ -421,40 +394,38 @@ def kernel(u, d, m): if ((i + j) & mask) == mask: id1 = i + j id2 = id1 + (1 << pos) - self._state[id1], self._state[id2] = kernel(self._state[id1], self._state[id2], m) + self._state[id1], self._state[id2] = kernel(self._state[id1], self._state[id2], matrix) - def _multi_qubit_gate(self, m, pos, mask): + def _multi_qubit_gate(self, matrix, pos, mask): # pylint: disable=too-many-locals """ - Applies the k-qubit gate matrix m to the qubits at `pos` - using `mask` to identify control qubits. + Applies the k-qubit gate matrix m to the qubits at `pos` using `mask` to identify control qubits. Args: - m (list[list]): 2^k x 2^k complex matrix describing the k-qubit - gate. + matrix (list[list]): 2^k x 2^k complex matrix describing the k-qubit gate. pos (list[int]): List of bit-positions of the qubits. mask (int): Bit-mask where set bits indicate control qubits. """ # follows the description in https://arxiv.org/abs/1704.01127 inactive = [p for p in range(len(self._map)) if p not in pos] - matrix = _np.matrix(m) + matrix = _np.matrix(matrix) subvec = _np.zeros(1 << len(pos), dtype=complex) subvec_idx = [0] * len(subvec) - for c in range(1 << len(inactive)): + for k in range(1 << len(inactive)): # determine base index (state of inactive qubits) base = 0 - for i in range(len(inactive)): - base |= ((c >> i) & 1) << inactive[i] + for i, _inactive in enumerate(inactive): + base |= ((k >> i) & 1) << _inactive # check the control mask if mask != (base & mask): continue # now gather all elements involved in mat-vec mul - for x in range(len(subvec_idx)): + for j in range(len(subvec_idx)): # pylint: disable=consider-using-enumerate offset = 0 - for i in range(len(pos)): - offset |= ((x >> i) & 1) << pos[i] - subvec_idx[x] = base | offset - subvec[x] = self._state[subvec_idx[x]] + for i, _pos in enumerate(pos): + offset |= ((j >> i) & 1) << _pos + subvec_idx[j] = base | offset + subvec[j] = self._state[subvec_idx[j]] # perform mat-vec mul self._state[subvec_idx] = matrix.dot(subvec) @@ -463,18 +434,18 @@ def set_wavefunction(self, wavefunction, ordering): Set wavefunction and qubit ordering. Args: - wavefunction (list[complex]): Array of complex amplitudes - describing the wavefunction (must be normalized). - ordering (list): List of ids describing the new ordering of qubits - (i.e., the ordering of the provided wavefunction). + wavefunction (list[complex]): Array of complex amplitudes describing the wavefunction (must be normalized). + ordering (list): List of ids describing the new ordering of qubits (i.e., the ordering of the provided + wavefunction). """ # wavefunction contains 2^n values for n qubits - assert len(wavefunction) == (1 << len(ordering)) + if len(wavefunction) != (1 << len(ordering)): # pragma: no cover + raise ValueError('The wavefunction must contain 2^n elements!') + # all qubits must have been allocated before - if not all([Id in self._map for Id in ordering]) or len(self._map) != len(ordering): + if not all(qubit_id in self._map for qubit_id in ordering) or len(self._map) != len(ordering): raise RuntimeError( - "set_wavefunction(): Invalid mapping provided." - " Please make sure all qubits have been " + "set_wavefunction(): Invalid mapping provided. Please make sure all qubits have been " "allocated previously (call eng.flush())." ) @@ -493,18 +464,18 @@ def collapse_wavefunction(self, ids, values): RuntimeError: If probability of outcome is ~0 or unknown qubits are provided. """ - assert len(ids) == len(values) + if len(ids) != len(values): + raise ValueError('The number of ids and values do not match!') # all qubits must have been allocated before - if not all([Id in self._map for Id in ids]): + if not all(Id in self._map for Id in ids): raise RuntimeError( - "collapse_wavefunction(): Unknown qubit id(s)" - " provided. Try calling eng.flush() before " + "collapse_wavefunction(): Unknown qubit id(s) provided. Try calling eng.flush() before " "invoking this function." ) mask = 0 val = 0 - for i in range(len(ids)): - pos = self._map[ids[i]] + for i, qubit_id in enumerate(ids): + pos = self._map[qubit_id] mask |= 1 << pos val |= int(values[i]) << pos nrm = 0.0 @@ -524,9 +495,8 @@ def run(self): """ Dummy function to implement the same interface as the c++ simulator. """ - pass - def _apply_term(self, term, ids, ctrlids=[]): + def _apply_term(self, term, ids, ctrlids=None): """ Applies a QubitOperator term to the state vector. (Helper function for time evolution & expectation) @@ -540,6 +510,8 @@ def _apply_term(self, term, ids, ctrlids=[]): Y = [[0.0, -1j], [1j, 0.0]] Z = [[1.0, 0.0], [0.0, -1.0]] gates = [X, Y, Z] + if not ctrlids: + ctrlids = [] for local_op in term: qb_id = ids[local_op[0]] self.apply_controlled_gate(gates[ord(local_op[1]) - ord('X')], [qb_id], ctrlids) diff --git a/projectq/backends/_sim/_simulator.py b/projectq/backends/_sim/_simulator.py index 3647136b6..cfc2b56ec 100755 --- a/projectq/backends/_sim/_simulator.py +++ b/projectq/backends/_sim/_simulator.py @@ -104,14 +104,13 @@ def is_available(self, cmd): cmd.gate == Measure or cmd.gate == Allocate or cmd.gate == Deallocate - or isinstance(cmd.gate, BasicMathGate) - or isinstance(cmd.gate, TimeEvolution) + or isinstance(cmd.gate, (BasicMathGate, TimeEvolution)) ): return True try: - m = cmd.gate.matrix + matrix = cmd.gate.matrix # Allow up to 5-qubit gates - if len(m) > 2 ** 5: + if len(matrix) > 2 ** 5: return False return True except AttributeError: @@ -133,8 +132,7 @@ def _convert_logical_to_mapped_qureg(self, qureg): new_qubit = WeakQubitRef(qubit.engine, mapper.current_mapping[qubit.id]) mapped_qureg.append(new_qubit) return mapped_qureg - else: - return qureg + return qureg def get_expectation_value(self, qubit_operator, qureg): """ @@ -335,7 +333,7 @@ def cheat(self): """ return self._simulator.cheat() - def _handle(self, cmd): + def _handle(self, cmd): # pylint: disable=too-many-branches,too-many-locals,too-many-statements """ Handle all commands, i.e., call the member functions of the C++- simulator object corresponding to measurement, allocation/ @@ -350,12 +348,13 @@ def _handle(self, cmd): """ if cmd.gate == Measure: - assert get_control_count(cmd) == 0 + if get_control_count(cmd) != 0: + raise ValueError('Cannot have control qubits with a measurement gate!') ids = [qb.id for qr in cmd.qubits for qb in qr] out = self._simulator.measure_qubits(ids) i = 0 - for qr in cmd.qubits: - for qb in qr: + for qureg in cmd.qubits: + for qb in qureg: # Check if a mapper assigned a different logical id logical_id_tag = None for tag in cmd.tags: @@ -366,23 +365,23 @@ def _handle(self, cmd): self.main_engine.set_measurement_result(qb, out[i]) i += 1 elif cmd.gate == Allocate: - ID = cmd.qubits[0][0].id - self._simulator.allocate_qubit(ID) + qubit_id = cmd.qubits[0][0].id + self._simulator.allocate_qubit(qubit_id) elif cmd.gate == Deallocate: - ID = cmd.qubits[0][0].id - self._simulator.deallocate_qubit(ID) + qubit_id = cmd.qubits[0][0].id + self._simulator.deallocate_qubit(qubit_id) elif isinstance(cmd.gate, BasicMathGate): # improve performance by using C++ code for some commomn gates - from projectq.libs.math import ( + from projectq.libs.math import ( # pylint: disable=import-outside-toplevel AddConstant, AddConstantModN, MultiplyByConstantModN, ) qubitids = [] - for qr in cmd.qubits: + for qureg in cmd.qubits: qubitids.append([]) - for qb in qr: + for qb in qureg: qubitids[-1].append(qb.id) if FALLBACK_TO_PYSIM: math_fun = cmd.gate.get_math_function(cmd.qubits) @@ -410,13 +409,13 @@ def _handle(self, cmd): self._simulator.emulate_math(math_fun, qubitids, [qb.id for qb in cmd.control_qubits]) elif isinstance(cmd.gate, TimeEvolution): op = [(list(term), coeff) for (term, coeff) in cmd.gate.hamiltonian.terms.items()] - t = cmd.gate.time + time = cmd.gate.time qubitids = [qb.id for qb in cmd.qubits[0]] ctrlids = [qb.id for qb in cmd.control_qubits] - self._simulator.emulate_time_evolution(op, t, qubitids, ctrlids) + self._simulator.emulate_time_evolution(op, time, qubitids, ctrlids) elif len(cmd.gate.matrix) <= 2 ** 5: matrix = cmd.gate.matrix - ids = [qb.id for qr in cmd.qubits for qb in qr] + ids = [qb.id for qureg in cmd.qubits for qb in qureg] if not 2 ** len(ids) == len(cmd.gate.matrix): raise Exception( "Simulator: Error applying {} gate: " diff --git a/projectq/backends/_sim/_simulator_test.py b/projectq/backends/_sim/_simulator_test.py index 0d3cae90f..4e6001e35 100755 --- a/projectq/backends/_sim/_simulator_test.py +++ b/projectq/backends/_sim/_simulator_test.py @@ -232,6 +232,11 @@ def test_simulator_functional_measurement(sim): bit_value_sum = sum([int(qubit) for qubit in qubits]) assert bit_value_sum == 0 or bit_value_sum == 5 + qb1 = WeakQubitRef(engine=eng, idx=qubits[0].id) + qb2 = WeakQubitRef(engine=eng, idx=qubits[1].id) + with pytest.raises(ValueError): + eng.backend._handle(Command(engine=eng, gate=Measure, qubits=([qb1],), controls=[qb2])) + def test_simulator_measure_mapped_qubit(sim): eng = MainEngine(sim, []) @@ -606,6 +611,10 @@ def test_simulator_collapse_wavefunction(sim, mapper): with pytest.raises(RuntimeError): eng.backend.collapse_wavefunction(qubits, [0] * 4) eng.flush() + + # mismatch in length: raises + with pytest.raises(ValueError): + eng.backend.collapse_wavefunction(qubits, [0] * 5) eng.backend.collapse_wavefunction(qubits, [0] * 4) assert pytest.approx(eng.backend.get_probability([0] * 4, qubits)) == 1.0 All(H) | qubits[1:] diff --git a/projectq/cengines/__init__.py b/projectq/cengines/__init__.py index d81b59cee..9b25fde78 100755 --- a/projectq/cengines/__init__.py +++ b/projectq/cengines/__init__.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ProjectQ module containing all compiler engines""" + from ._basics import BasicEngine, LastEngineException, ForwarderEngine from ._cmdmodifier import CommandModifier from ._basicmapper import BasicMapperEngine diff --git a/projectq/cengines/_basicmapper.py b/projectq/cengines/_basicmapper.py index 0a70b9b26..876717138 100644 --- a/projectq/cengines/_basicmapper.py +++ b/projectq/cengines/_basicmapper.py @@ -15,37 +15,43 @@ """ Defines the parent class from which all mappers should be derived. -There is only one engine currently allowed to be derived from -BasicMapperEngine. This allows the simulator to automatically translate -logical qubit ids to mapped ids. +There is only one engine currently allowed to be derived from BasicMapperEngine. This allows the simulator to +automatically translate logical qubit ids to mapped ids. """ from copy import deepcopy -from projectq.cengines import BasicEngine, CommandModifier from projectq.meta import drop_engine_after, insert_engine, LogicalQubitIDTag from projectq.ops import MeasureGate +from ._basics import BasicEngine +from ._cmdmodifier import CommandModifier + class BasicMapperEngine(BasicEngine): """ Parent class for all Mappers. Attributes: - self.current_mapping (dict): Keys are the logical qubit ids and values - are the mapped qubit ids. + self.current_mapping (dict): Keys are the logical qubit ids and values are the mapped qubit ids. """ def __init__(self): - BasicEngine.__init__(self) + super().__init__() self._current_mapping = None @property def current_mapping(self): + """ + Access the current mapping + """ return deepcopy(self._current_mapping) @current_mapping.setter def current_mapping(self, current_mapping): + """ + Set the current mapping + """ self._current_mapping = current_mapping def _send_cmd_with_mapped_ids(self, cmd): @@ -67,8 +73,6 @@ def _send_cmd_with_mapped_ids(self, cmd): for qubit in control_qubits: qubit.id = self.current_mapping[qubit.id] if isinstance(new_cmd.gate, MeasureGate): - assert len(new_cmd.qubits) == 1 and len(new_cmd.qubits[0]) == 1 - # Add LogicalQubitIDTag to MeasureGate def add_logical_id(command, old_tags=deepcopy(cmd.tags)): command.tags = old_tags + [LogicalQubitIDTag(cmd.qubits[0][0].id)] @@ -82,5 +86,6 @@ def add_logical_id(command, old_tags=deepcopy(cmd.tags)): self.send([new_cmd]) def receive(self, command_list): + """Forward all commands to the next engine.""" for cmd in command_list: self._send_cmd_with_mapped_ids(cmd) diff --git a/projectq/cengines/_basics.py b/projectq/cengines/_basics.py index 9ac53bb6f..72ddcfaab 100755 --- a/projectq/cengines/_basics.py +++ b/projectq/cengines/_basics.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Module containing the basic definition of a compiler engine""" + from projectq.ops import Allocate, Deallocate from projectq.types import Qubit, Qureg, WeakQubitRef from projectq.ops import Command @@ -20,35 +22,28 @@ class LastEngineException(Exception): """ - Exception thrown when the last engine tries to access the next one. - (Next engine does not exist) + Exception thrown when the last engine tries to access the next one. (Next engine does not exist) - The default implementation of isAvailable simply asks the next engine - whether the command is available. An engine which legally may be the last - engine, this behavior needs to be adapted (see BasicEngine.isAvailable). + The default implementation of isAvailable simply asks the next engine whether the command is available. An engine + which legally may be the last engine, this behavior needs to be adapted (see BasicEngine.isAvailable). """ def __init__(self, engine): - Exception.__init__( - self, + super().__init__( ( - "\nERROR: Sending to next engine failed. " - "{} as last engine?\nIf this is legal, " - "please override 'isAvailable' to adapt its" - " behavior." + "\nERROR: Sending to next engine failed. {} as last engine?\nIf this is legal, please override" + "'isAvailable' to adapt its behavior." ).format(engine.__class__.__name__), ) -class BasicEngine(object): +class BasicEngine: """ - Basic compiler engine: All compiler engines are derived from this class. - It provides basic functionality such as qubit allocation/deallocation and - functions that provide information about the engine's position (e.g., next + Basic compiler engine: All compiler engines are derived from this class. It provides basic functionality such as + qubit allocation/deallocation and functions that provide information about the engine's position (e.g., next engine). - This information is provided by the MainEngine, which initializes all - further engines. + This information is provided by the MainEngine, which initializes all further engines. Attributes: next_engine (BasicEngine): Next compiler engine (or the back-end). @@ -60,8 +55,7 @@ def __init__(self): """ Initialize the basic engine. - Initializes local variables such as _next_engine, _main_engine, etc. to - None. + Initializes local variables such as _next_engine, _main_engine, etc. to None. """ self.main_engine = None self.next_engine = None @@ -69,9 +63,8 @@ def __init__(self): def is_available(self, cmd): """ - Default implementation of is_available: - Ask the next engine whether a command is available, i.e., - whether it can be executed by the next engine(s). + Default implementation of is_available: Ask the next engine whether a command is available, i.e., whether it + can be executed by the next engine(s). Args: cmd (Command): Command for which to check availability. @@ -80,13 +73,11 @@ def is_available(self, cmd): True if the command can be executed. Raises: - LastEngineException: If is_last_engine is True but is_available - is not implemented. + LastEngineException: If is_last_engine is True but is_available is not implemented. """ if not self.is_last_engine: return self.next_engine.is_available(cmd) - else: - raise LastEngineException(self) + raise LastEngineException(self) def allocate_qubit(self, dirty=False): """ @@ -117,7 +108,7 @@ def allocate_qubit(self, dirty=False): qb = Qureg([Qubit(self, new_id)]) cmd = Command(self, Allocate, (qb,)) if dirty: - from projectq.meta import DirtyQubitTag + from projectq.meta import DirtyQubitTag # pylint: disable=import-outside-toplevel if self.is_meta_tag_supported(DirtyQubitTag): cmd.tags += [DirtyQubitTag()] @@ -126,23 +117,21 @@ def allocate_qubit(self, dirty=False): self.send([cmd]) return qb - def allocate_qureg(self, n): + def allocate_qureg(self, n_qubits): """ - Allocate n qubits and return them as a quantum register, which is a - list of qubit objects. + Allocate n qubits and return them as a quantum register, which is a list of qubit objects. Args: n (int): Number of qubits to allocate Returns: Qureg of length n, a list of n newly allocated qubits. """ - return Qureg([self.allocate_qubit()[0] for _ in range(n)]) + return Qureg([self.allocate_qubit()[0] for _ in range(n_qubits)]) def deallocate_qubit(self, qubit): """ - Deallocate a qubit (and sends the deallocation command down the - pipeline). If the qubit was allocated as a dirty qubit, add - DirtyQubitTag() to Deallocate command. + Deallocate a qubit (and sends the deallocation command down the pipeline). If the qubit was allocated as a + dirty qubit, add DirtyQubitTag() to Deallocate command. Args: qubit (BasicQubit): Qubit to deallocate. @@ -152,7 +141,7 @@ def deallocate_qubit(self, qubit): if qubit.id == -1: raise ValueError("Already deallocated.") - from projectq.meta import DirtyQubitTag + from projectq.meta import DirtyQubitTag # pylint: disable=import-outside-toplevel is_dirty = qubit.id in self.main_engine.dirty_qubits self.send( @@ -173,14 +162,12 @@ def is_meta_tag_supported(self, meta_tag): Check if there is a compiler engine handling the meta tag Args: - engine: First engine to check (then iteratively calls - getNextEngine) + engine: First engine to check (then iteratively calls getNextEngine) meta_tag: Meta tag class for which to check support Returns: - supported (bool): True if one of the further compiler engines is a - meta tag handler, i.e., engine.is_meta_tag_handler(meta_tag) - returns True. + supported (bool): True if one of the further compiler engines is a meta tag handler, i.e., + engine.is_meta_tag_handler(meta_tag) returns True. """ engine = self while engine is not None: @@ -202,11 +189,10 @@ def send(self, command_list): class ForwarderEngine(BasicEngine): """ - A ForwarderEngine is a trivial engine which forwards all commands to the - next engine. + A ForwarderEngine is a trivial engine which forwards all commands to the next engine. - It is mainly used as a substitute for the MainEngine at lower levels such - that meta operations still work (e.g., with Compute). + It is mainly used as a substitute for the MainEngine at lower levels such that meta operations still work (e.g., + with Compute). """ def __init__(self, engine, cmd_mod_fun=None): @@ -215,11 +201,10 @@ def __init__(self, engine, cmd_mod_fun=None): Args: engine (BasicEngine): Engine to forward all commands to. - cmd_mod_fun (function): Function which is called before sending a - command. Each command cmd is replaced by the command it - returns when getting called with cmd. + cmd_mod_fun (function): Function which is called before sending a command. Each command cmd is replaced by + the command it returns when getting called with cmd. """ - BasicEngine.__init__(self) + super().__init__() self.main_engine = engine.main_engine self.next_engine = engine if cmd_mod_fun is None: diff --git a/projectq/cengines/_cmdmodifier.py b/projectq/cengines/_cmdmodifier.py index faf2440c6..c988cccd6 100755 --- a/projectq/cengines/_cmdmodifier.py +++ b/projectq/cengines/_cmdmodifier.py @@ -13,17 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Contains a CommandModifier engine, which can be used to, e.g., modify the tags -of all commands which pass by (see the AutoReplacer for an example). +Contains a CommandModifier engine, which can be used to, e.g., modify the tags of all commands which pass by (see the +AutoReplacer for an example). """ -from projectq.cengines import BasicEngine + +from ._basics import BasicEngine class CommandModifier(BasicEngine): """ - CommandModifier is a compiler engine which applies a function to all - incoming commands, sending on the resulting command instead of the - original one. + CommandModifier is a compiler engine which applies a function to all incoming commands, sending on the resulting + command instead of the original one. """ def __init__(self, cmd_mod_fun): @@ -31,8 +31,7 @@ def __init__(self, cmd_mod_fun): Initialize the CommandModifier. Args: - cmd_mod_fun (function): Function which, given a command cmd, - returns the command it should send instead. + cmd_mod_fun (function): Function which, given a command cmd, returns the command it should send instead. Example: .. code-block:: python @@ -42,17 +41,15 @@ def cmd_mod_fun(cmd): compiler_engine = CommandModifier(cmd_mod_fun) ... """ - BasicEngine.__init__(self) + super().__init__() self._cmd_mod_fun = cmd_mod_fun def receive(self, command_list): """ - Receive a list of commands from the previous engine, modify all - commands, and send them on to the next engine. + Receive a list of commands from the previous engine, modify all commands, and send them on to the next engine. Args: - command_list (list): List of commands to receive and then - (after modification) send on. + command_list (list): List of commands to receive and then (after modification) send on. """ new_command_list = [self._cmd_mod_fun(cmd) for cmd in command_list] self.send(new_command_list) diff --git a/projectq/cengines/_ibm5qubitmapper.py b/projectq/cengines/_ibm5qubitmapper.py index 3b75c329b..4f9d093e5 100755 --- a/projectq/cengines/_ibm5qubitmapper.py +++ b/projectq/cengines/_ibm5qubitmapper.py @@ -17,12 +17,14 @@ """ import itertools -from projectq.cengines import BasicMapperEngine from projectq.ops import FlushGate, NOT, Allocate from projectq.meta import get_control_count from projectq.backends import IBMBackend +from ._basicmapper import BasicMapperEngine + + class IBM5QubitMapper(BasicMapperEngine): """ Mapper for the 5-qubit IBM backend. @@ -44,7 +46,7 @@ def __init__(self, connections=None): Resets the mapping. """ - BasicMapperEngine.__init__(self) + super().__init__() self.current_mapping = dict() self._reset() self._cmds = [] diff --git a/projectq/cengines/_linearmapper.py b/projectq/cengines/_linearmapper.py index cd2d1149d..911b5e975 100644 --- a/projectq/cengines/_linearmapper.py +++ b/projectq/cengines/_linearmapper.py @@ -15,17 +15,14 @@ """ Mapper for a quantum circuit to a linear chain of qubits. -Input: Quantum circuit with 1 and 2 qubit gates on n qubits. Gates are assumed - to be applied in parallel if they act on disjoint qubit(s) and any pair - of qubits can perform a 2 qubit gate (all-to-all connectivity) -Output: Quantum circuit in which qubits are placed in 1-D chain in which only - nearest neighbour qubits can perform a 2 qubit gate. The mapper uses - Swap gates in order to move qubits next to each other. +Input: Quantum circuit with 1 and 2 qubit gates on n qubits. Gates are assumed to be applied in parallel if they act + on disjoint qubit(s) and any pair of qubits can perform a 2 qubit gate (all-to-all connectivity) +Output: Quantum circuit in which qubits are placed in 1-D chain in which only nearest neighbour qubits can perform a 2 + qubit gate. The mapper uses Swap gates in order to move qubits next to each other. """ from copy import deepcopy -from projectq.cengines import BasicMapperEngine from projectq.meta import LogicalQubitIDTag from projectq.ops import ( Allocate, @@ -38,14 +35,15 @@ ) from projectq.types import WeakQubitRef +from ._basicmapper import BasicMapperEngine + def return_swap_depth(swaps): """ Returns the circuit depth to execute these swaps. Args: - swaps(list of tuples): Each tuple contains two integers representing - the two IDs of the qubits involved in the + swaps(list of tuples): Each tuple contains two integers representing the two IDs of the qubits involved in the Swap operation Returns: Circuit depth to execute these swaps. @@ -62,30 +60,27 @@ def return_swap_depth(swaps): return max(list(depth_of_qubits.values()) + [0]) -class LinearMapper(BasicMapperEngine): +class LinearMapper(BasicMapperEngine): # pylint: disable=too-many-instance-attributes """ Maps a quantum circuit to a linear chain of nearest neighbour interactions. - Maps a quantum circuit to a linear chain of qubits with nearest neighbour - interactions using Swap gates. It supports open or cyclic boundary - conditions. + Maps a quantum circuit to a linear chain of qubits with nearest neighbour interactions using Swap gates. It + supports open or cyclic boundary conditions. Attributes: - current_mapping: Stores the mapping: key is logical qubit id, value - is mapped qubit id from 0,...,self.num_qubits + current_mapping: Stores the mapping: key is logical qubit id, value is mapped qubit id from + 0,...,self.num_qubits cyclic (Bool): If chain is cyclic or not storage (int): Number of gate it caches before mapping. num_mappings (int): Number of times the mapper changed the mapping - depth_of_swaps (dict): Key are circuit depth of swaps, value is the - number of such mappings which have been + depth_of_swaps (dict): Key are circuit depth of swaps, value is the number of such mappings which have been applied - num_of_swaps_per_mapping (dict): Key are the number of swaps per - mapping, value is the number of such - mappings which have been applied + num_of_swaps_per_mapping (dict): Key are the number of swaps per mapping, value is the number of such mappings + which have been applied Note: - 1) Gates are cached and only mapped from time to time. A - FastForwarding gate doesn't empty the cache, only a FlushGate does. + 1) Gates are cached and only mapped from time to time. A FastForwarding gate doesn't empty the cache, only a + FlushGate does. 2) Only 1 and two qubit gates allowed. 3) Does not optimize for dirty qubits. """ @@ -99,7 +94,7 @@ def __init__(self, num_qubits, cyclic=False, storage=1000): cyclic(bool): If 1D chain is a cycle. Default is False. storage(int): Number of gates to temporarily store, default is 1000 """ - BasicMapperEngine.__init__(self) + super().__init__() self.num_qubits = num_qubits self.cyclic = cyclic self.storage = storage @@ -121,38 +116,26 @@ def is_available(self, cmd): num_qubits = 0 for qureg in cmd.all_qubits: num_qubits += len(qureg) - if num_qubits <= 2: - return True - else: - return False + return num_qubits <= 2 @staticmethod def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_commands, current_mapping): """ Builds a mapping of qubits to a linear chain. - It goes through stored_commands and tries to find a - mapping to apply these gates on a first come first served basis. - More compilicated scheme could try to optimize to apply as many gates - as possible between the Swaps. + It goes through stored_commands and tries to find a mapping to apply these gates on a first come first served + basis. More compilicated scheme could try to optimize to apply as many gates as possible between the Swaps. Args: num_qubits(int): Total number of qubits in the linear chain cyclic(bool): If linear chain is a cycle. - currently_allocated_ids(set of int): Logical qubit ids for which - the Allocate gate has already - been processed and sent to - the next engine but which are - not yet deallocated and hence - need to be included in the - new mapping. - stored_commands(list of Command objects): Future commands which - should be applied next. - current_mapping: A current mapping as a dict. key is logical qubit - id, value is placement id. If there are different - possible maps, this current mapping is used to - minimize the swaps to go to the new mapping by a - heuristic. + currently_allocated_ids(set of int): Logical qubit ids for which the Allocate gate has already been + processed and sent to the next engine but which are not yet + deallocated and hence need to be included in the new mapping. + stored_commands(list of Command objects): Future commands which should be applied next. + current_mapping: A current mapping as a dict. key is logical qubit id, value is placement id. If there are + different possible maps, this current mapping is used to minimize the swaps to go to the + new mapping by a heuristic. Returns: A new mapping as a dict. key is logical qubit id, value is placement id @@ -183,7 +166,7 @@ def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_comma if len(qubit_ids) > 2 or len(qubit_ids) == 0: raise Exception("Invalid command (number of qubits): " + str(cmd)) - elif isinstance(cmd.gate, AllocateQubitGate): + if isinstance(cmd.gate, AllocateQubitGate): qubit_id = cmd.qubits[0][0].id if len(allocated_qubits) < num_qubits: allocated_qubits.add(qubit_id) @@ -221,29 +204,30 @@ def return_new_mapping(num_qubits, cyclic, currently_allocated_ids, stored_comma ) @staticmethod - def _process_two_qubit_gate(num_qubits, cyclic, qubit0, qubit1, active_qubits, segments, neighbour_ids): + def _process_two_qubit_gate( # pylint: disable=too-many-arguments,too-many-branches,too-many-statements + num_qubits, cyclic, qubit0, qubit1, active_qubits, segments, neighbour_ids + ): """ Processes a two qubit gate. - It either removes the two qubits from active_qubits if the gate is not - possible or updates the segements such that the gate is possible. + It either removes the two qubits from active_qubits if the gate is not possible or updates the segements such + that the gate is possible. Args: num_qubits (int): Total number of qubits in the chain cyclic (bool): If linear chain is a cycle qubit0 (int): qubit.id of one of the qubits qubit1 (int): qubit.id of the other qubit - active_qubits (set): contains all qubit ids which for which gates - can be applied in this cycle before the swaps - segments: List of segments. A segment is a list of neighbouring - qubits. + active_qubits (set): contains all qubit ids which for which gates can be applied in this cycle before the + swaps + segments: List of segments. A segment is a list of neighbouring qubits. neighbour_ids (dict): Key: qubit.id Value: qubit.id of neighbours """ # already connected if qubit1 in neighbour_ids and qubit0 in neighbour_ids[qubit1]: return # at least one qubit is not an active qubit: - elif qubit0 not in active_qubits or qubit1 not in active_qubits: + if qubit0 not in active_qubits or qubit1 not in active_qubits: active_qubits.discard(qubit0) active_qubits.discard(qubit1) # at least one qubit is in the inside of a segment: @@ -328,30 +312,25 @@ def _process_two_qubit_gate(num_qubits, cyclic, qubit0, qubit1, active_qubits, s return @staticmethod - def _return_new_mapping_from_segments(num_qubits, segments, allocated_qubits, current_mapping): + def _return_new_mapping_from_segments( # pylint: disable=too-many-locals,too-many-branches + num_qubits, segments, allocated_qubits, current_mapping + ): """ Combines the individual segments into a new mapping. - It tries to minimize the number of swaps to go from the old mapping - in self.current_mapping to the new mapping which it returns. The - strategy is to map a segment to the same region where most of the - qubits are already. Note that this is not a global optimal strategy - but helps if currently the qubits can be divided into independent - groups without interactions between the groups. + It tries to minimize the number of swaps to go from the old mapping in self.current_mapping to the new mapping + which it returns. The strategy is to map a segment to the same region where most of the qubits are + already. Note that this is not a global optimal strategy but helps if currently the qubits can be divided into + independent groups without interactions between the groups. Args: num_qubits (int): Total number of qubits in the linear chain - segments: List of segments. A segment is a list of qubit ids which - should be nearest neighbour in the new map. - Individual qubits are in allocated_qubits but not in - any segment - allocated_qubits: A set of all qubit ids which need to be present - in the new map - current_mapping: A current mapping as a dict. key is logical qubit - id, value is placement id. If there are different - possible maps, this current mapping is used to - minimize the swaps to go to the new mapping by a - heuristic. + segments: List of segments. A segment is a list of qubit ids which should be nearest neighbour in the new + map. Individual qubits are in allocated_qubits but not in any segment + allocated_qubits: A set of all qubit ids which need to be present in the new map + current_mapping: A current mapping as a dict. key is logical qubit id, value is placement id. If there are + different possible maps, this current mapping is used to minimize the swaps to go to the + new mapping by a heuristic. Returns: A new mapping as a dict. key is logical qubit id, value is placement id @@ -427,13 +406,11 @@ def _odd_even_transposition_sort_swaps(self, old_mapping, new_mapping): See https://en.wikipedia.org/wiki/Odd-even_sort for more info. Args: - old_mapping: dict: keys are logical ids and values are mapped - qubit ids - new_mapping: dict: keys are logical ids and values are mapped - qubit ids + old_mapping: dict: keys are logical ids and values are mapped qubit ids + new_mapping: dict: keys are logical ids and values are mapped qubit ids Returns: - List of tuples. Each tuple is a swap operation which needs to be - applied. Tuple contains the two MappedQubit ids for the Swap. + List of tuples. Each tuple is a swap operation which needs to be applied. Tuple contains the two + MappedQubit ids for the Swap. """ final_positions = [None] * self.num_qubits # move qubits which are in both mappings @@ -446,10 +423,11 @@ def _odd_even_transposition_sort_swaps(self, old_mapping, new_mapping): all_ids = set(range(self.num_qubits)) not_used_mapped_ids = list(all_ids.difference(used_mapped_ids)) not_used_mapped_ids = sorted(not_used_mapped_ids, reverse=True) - for i in range(len(final_positions)): - if final_positions[i] is None: + for i, pos in enumerate(final_positions): + if pos is None: final_positions[i] = not_used_mapped_ids.pop() - assert len(not_used_mapped_ids) == 0 + if len(not_used_mapped_ids) > 0: # pragma: no cover + raise RuntimeError('Internal compiler error: len(not_used_mapped_ids) > 0') # Start sorting: swap_operations = [] finished_sorting = False @@ -471,7 +449,7 @@ def _odd_even_transposition_sort_swaps(self, old_mapping, new_mapping): finished_sorting = False return swap_operations - def _send_possible_commands(self): + def _send_possible_commands(self): # pylint: disable=too-many-branches """ Sends the stored commands possible without changing the mapping. @@ -529,7 +507,7 @@ def _send_possible_commands(self): mapped_ids = list(mapped_ids) diff = abs(mapped_ids[0] - mapped_ids[1]) if self.cyclic: - if diff != 1 and diff != self.num_qubits - 1: + if diff not in (1, self.num_qubits - 1): send_gate = False else: if diff != 1: @@ -543,15 +521,13 @@ def _send_possible_commands(self): new_stored_commands.append(cmd) self._stored_commands = new_stored_commands - def _run(self): + def _run(self): # pylint: disable=too-many-locals,too-many-branches """ Creates a new mapping and executes possible gates. - It first allocates all 0, ..., self.num_qubits-1 mapped qubit ids, if - they are not already used because we might need them all for the - swaps. Then it creates a new map, swaps all the qubits to the new map, - executes all possible gates, and finally deallocates mapped qubit ids - which don't store any information. + It first allocates all 0, ..., self.num_qubits-1 mapped qubit ids, if they are not already used because we + might need them all for the swaps. Then it creates a new map, swaps all the qubits to the new map, executes + all possible gates, and finally deallocates mapped qubit ids which don't store any information. """ num_of_stored_commands_before = len(self._stored_commands) if not self.current_mapping: @@ -581,9 +557,9 @@ def _run(self): self.send([cmd]) # Send swap operations to arrive at new_mapping: for qubit_id0, qubit_id1 in swaps: - q0 = WeakQubitRef(engine=self, idx=qubit_id0) - q1 = WeakQubitRef(engine=self, idx=qubit_id1) - cmd = Command(engine=self, gate=Swap, qubits=([q0], [q1])) + qb0 = WeakQubitRef(engine=self, idx=qubit_id0) + qb1 = WeakQubitRef(engine=self, idx=qubit_id1) + cmd = Command(engine=self, gate=Swap, qubits=([qb0], [qb1])) self.send([cmd]) # Register statistics: self.num_mappings += 1 @@ -613,24 +589,21 @@ def _run(self): # Check that mapper actually made progress if len(self._stored_commands) == num_of_stored_commands_before: raise RuntimeError( - "Mapper is potentially in an infinite loop. " - "It is likely that the algorithm requires " - "too many qubits. Increase the number of " - "qubits for this mapper." + "Mapper is potentially in an infinite loop. It is likely that the algorithm requires too many" + "qubits. Increase the number of qubits for this mapper." ) def receive(self, command_list): """ - Receives a command list and, for each command, stores it until - we do a mapping (FlushGate or Cache of stored commands is full). + Receives a command list and, for each command, stores it until we do a mapping (FlushGate or Cache of stored + commands is full). Args: - command_list (list of Command objects): list of commands to - receive. + command_list (list of Command objects): list of commands to receive. """ for cmd in command_list: if isinstance(cmd.gate, FlushGate): - while len(self._stored_commands): + while self._stored_commands: self._run() self.send([cmd]) else: diff --git a/projectq/cengines/_main.py b/projectq/cengines/_main.py index 475207dd1..2c961b9a6 100755 --- a/projectq/cengines/_main.py +++ b/projectq/cengines/_main.py @@ -21,28 +21,38 @@ import traceback import weakref -from projectq.cengines import BasicEngine, BasicMapperEngine from projectq.ops import Command, FlushGate from projectq.types import WeakQubitRef from projectq.backends import Simulator +from ._basics import BasicEngine +from ._basicmapper import BasicMapperEngine + class NotYetMeasuredError(Exception): - pass + """Exception raised when trying to access the measurement value of a qubit that has not yet been measured.""" class UnsupportedEngineError(Exception): - pass + """Exception raised when a non-supported compiler engine is encountered""" + + +class _ErrorEngine: # pylint: disable=too-few-public-methods + """ + Fake compiler engine class only used to ensure gracious failure when an exception occurs in the MainEngine + constructor. + """ + + def receive(self, command_list): # pylint: disable=unused-argument + """No-op""" -class MainEngine(BasicEngine): +class MainEngine(BasicEngine): # pylint: disable=too-many-instance-attributes """ - The MainEngine class provides all functionality of the main compiler - engine. + The MainEngine class provides all functionality of the main compiler engine. - It initializes all further compiler engines (calls, e.g., - .next_engine=...) and keeps track of measurement results and active - qubits (and their IDs). + It initializes all further compiler engines (calls, e.g., .next_engine=...) and keeps track of measurement results + and active qubits (and their IDs). Attributes: next_engine (BasicEngine): Next compiler engine (or the back-end). @@ -58,14 +68,13 @@ def __init__(self, backend=None, engine_list=None, verbose=False): """ Initialize the main compiler engine and all compiler engines. - Sets 'next_engine'- and 'main_engine'-attributes of all compiler - engines and adds the back-end as the last engine. + Sets 'next_engine'- and 'main_engine'-attributes of all compiler engines and adds the back-end as the last + engine. Args: backend (BasicEngine): Backend to send the compiled circuit to. - engine_list (list): List of engines / backends to use - as compiler engines. Note: The engine list must not contain - multiple mappers (instances of BasicMapperEngine). + engine_list (list): List of engines / backends to use as compiler engines. Note: The engine + list must not contain multiple mappers (instances of BasicMapperEngine). Default: projectq.setups.default.get_engine_list() verbose (bool): Either print full or compact error messages. Default: False (i.e. compact error messages). @@ -103,12 +112,18 @@ def __init__(self, backend=None, engine_list=None, verbose=False): LocalOptimizer(3)] eng = MainEngine(Simulator(), engines) """ - BasicEngine.__init__(self) + super().__init__() + self.active_qubits = weakref.WeakSet() + self._measurements = dict() + self.dirty_qubits = set() + self.verbose = verbose + self.main_engine = self if backend is None: backend = Simulator() else: # Test that backend is BasicEngine object if not isinstance(backend, BasicEngine): + self.next_engine = _ErrorEngine() raise UnsupportedEngineError( "\nYou supplied a backend which is not supported,\n" "i.e. not an instance of BasicEngine.\n" @@ -116,9 +131,11 @@ def __init__(self, backend=None, engine_list=None, verbose=False): "E.g. MainEngine(backend=Simulator) instead of \n" " MainEngine(backend=Simulator())" ) + self.backend = backend + # default engine_list is projectq.setups.default.get_engine_list() if engine_list is None: - import projectq.setups.default + import projectq.setups.default # pylint: disable=import-outside-toplevel engine_list = projectq.setups.default.get_engine_list() @@ -127,26 +144,29 @@ def __init__(self, backend=None, engine_list=None, verbose=False): # Test that engine list elements are all BasicEngine objects for current_eng in engine_list: if not isinstance(current_eng, BasicEngine): + self.next_engine = _ErrorEngine() raise UnsupportedEngineError( "\nYou supplied an unsupported engine in engine_list," "\ni.e. not an instance of BasicEngine.\n" "Did you forget the brackets to create an instance?\n" - "E.g. MainEngine(engine_list=[AutoReplacer]) instead " - "of\n MainEngine(engine_list=[AutoReplacer()])" + "E.g. MainEngine(engine_list=[AutoReplacer]) instead of\n" + " MainEngine(engine_list=[AutoReplacer()])" ) if isinstance(current_eng, BasicMapperEngine): if self.mapper is None: self.mapper = current_eng else: + self.next_engine = _ErrorEngine() raise UnsupportedEngineError("More than one mapper engine is not supported.") else: + self.next_engine = _ErrorEngine() raise UnsupportedEngineError("The provided list of engines is not a list!") engine_list = engine_list + [backend] - self.backend = backend # Test that user did not supply twice the same engine instance - num_different_engines = len(set([id(item) for item in engine_list])) + num_different_engines = len(set(id(item) for item in engine_list)) if len(engine_list) != num_different_engines: + self.next_engine = _ErrorEngine() raise UnsupportedEngineError( "\nError:\n You supplied twice the same engine as backend" " or item in engine_list. This doesn't work. Create two \n" @@ -161,11 +181,6 @@ def __init__(self, backend=None, engine_list=None, verbose=False): engine_list[-1].main_engine = self engine_list[-1].is_last_engine = True self.next_engine = engine_list[0] - self.main_engine = self - self.active_qubits = weakref.WeakSet() - self._measurements = dict() - self.dirty_qubits = set() - self.verbose = verbose # In order to terminate an example code without eng.flush def atexit_function(weakref_main_eng): @@ -173,9 +188,8 @@ def atexit_function(weakref_main_eng): if eng is not None: if not hasattr(sys, "last_type"): eng.flush(deallocate_qubits=True) - # An exception causes the termination, don't send a flush and - # make sure no qubits send deallocation gates anymore as this - # might trigger additional exceptions + # An exception causes the termination, don't send a flush and make sure no qubits send deallocation + # gates anymore as this might trigger additional exceptions else: for qubit in eng.active_qubits: qubit.id = -1 @@ -188,8 +202,7 @@ def __del__(self): """ Destroy the main engine. - Flushes the entire circuit down the pipeline, clearing all temporary - buffers (in, e.g., optimizers). + Flushes the entire circuit down the pipeline, clearing all temporary buffers (in, e.g., optimizers). """ if not hasattr(sys, "last_type"): self.flush(deallocate_qubits=True) @@ -202,23 +215,19 @@ def set_measurement_result(self, qubit, value): """ Register a measurement result - The engine being responsible for measurement results needs to register - these results with the master engine such that they are available when - the user calls an int() or bool() conversion operator on a measured - qubit. + The engine being responsible for measurement results needs to register these results with the master engine + such that they are available when the user calls an int() or bool() conversion operator on a measured qubit. Args: - qubit (BasicQubit): Qubit for which to register the measurement - result. - value (bool): Boolean value of the measurement outcome - (True / False = 1 / 0 respectively). + qubit (BasicQubit): Qubit for which to register the measurement result. + value (bool): Boolean value of the measurement outcome (True / False = 1 / 0 respectively). """ self._measurements[qubit.id] = bool(value) def get_measurement_result(self, qubit): """ - Return the classical value of a measured qubit, given that an engine - registered this result previously (see setMeasurementResult). + Return the classical value of a measured qubit, given that an engine registered this result previously (see + setMeasurementResult). Args: qubit (BasicQubit): Qubit of which to get the measurement result. @@ -236,17 +245,13 @@ def get_measurement_result(self, qubit): """ if qubit.id in self._measurements: return self._measurements[qubit.id] - else: - raise NotYetMeasuredError( - "\nError: Can't access measurement result for " - "qubit #" + str(qubit.id) + ". The problem may " - "be:\n\t1. Your " - "code lacks a measurement statement\n\t" - "2. You have not yet called engine.flush() to " - "force execution of your code\n\t3. The " - "underlying backend failed to register " - "the measurement result\n" - ) + raise NotYetMeasuredError( + "\nError: Can't access measurement result for qubit #" + str(qubit.id) + ". The problem may be:\n\t" + "1. Your code lacks a measurement statement\n\t" + "2. You have not yet called engine.flush() to force execution of your code\n\t" + "3. The " + "underlying backend failed to register the measurement result\n" + ) def get_new_qubit_id(self): """ @@ -276,28 +281,25 @@ def send(self, command_list): """ try: self.next_engine.receive(command_list) - except Exception: + except Exception as err: # pylint: disable=broad-except if self.verbose: raise - else: - exc_type, exc_value, exc_traceback = sys.exc_info() - # try: - last_line = traceback.format_exc().splitlines() - compact_exception = exc_type( - str(exc_value) + '\n raised in:\n' + repr(last_line[-3]) + "\n" + repr(last_line[-2]) - ) - compact_exception.__cause__ = None - raise compact_exception # use verbose=True for more info + exc_type, exc_value, _ = sys.exc_info() + # try: + last_line = traceback.format_exc().splitlines() + compact_exception = exc_type( + str(exc_value) + '\n raised in:\n' + repr(last_line[-3]) + "\n" + repr(last_line[-2]) + ) + compact_exception.__cause__ = None + raise compact_exception from err # use verbose=True for more info def flush(self, deallocate_qubits=False): """ - Flush the entire circuit down the pipeline, clearing potential buffers - (of, e.g., optimizers). + Flush the entire circuit down the pipeline, clearing potential buffers (of, e.g., optimizers). Args: - deallocate_qubits (bool): If True, deallocates all qubits that are - still alive (invalidating references to them by setting their - id to -1). + deallocate_qubits (bool): If True, deallocates all qubits that are still alive (invalidating references to + them by setting their id to -1). """ if deallocate_qubits: while [qb for qb in self.active_qubits if qb is not None]: diff --git a/projectq/cengines/_main_test.py b/projectq/cengines/_main_test.py index 078ccad6e..3178cd5c7 100755 --- a/projectq/cengines/_main_test.py +++ b/projectq/cengines/_main_test.py @@ -95,7 +95,7 @@ def test_main_engine_del(): sys.last_type = None del sys.last_type # need engine which caches commands to test that del calls flush - caching_engine = LocalOptimizer(m=5) + caching_engine = LocalOptimizer(cache_size=5) backend = DummyEngine(save_commands=True) eng = _main.MainEngine(backend=backend, engine_list=[caching_engine]) qubit = eng.allocate_qubit() diff --git a/projectq/cengines/_manualmapper.py b/projectq/cengines/_manualmapper.py index 75364efdb..4af0122ac 100755 --- a/projectq/cengines/_manualmapper.py +++ b/projectq/cengines/_manualmapper.py @@ -15,45 +15,42 @@ """ Contains a compiler engine to add mapping information """ -from projectq.cengines import BasicMapperEngine +from ._basicmapper import BasicMapperEngine class ManualMapper(BasicMapperEngine): """ - Manual Mapper which adds QubitPlacementTags to Allocate gate commands - according to a user-specified mapping. + Manual Mapper which adds QubitPlacementTags to Allocate gate commands according to a user-specified mapping. Attributes: - map (function): The function which maps a given qubit id to its - location. It gets set when initializing the mapper. + map (function): The function which maps a given qubit id to its location. It gets set when initializing the + mapper. """ def __init__(self, map_fun=lambda x: x): """ - Initialize the mapper to a given mapping. If no mapping function is - provided, the qubit id is used as the location. + Initialize the mapper to a given mapping. If no mapping function is provided, the qubit id is used as the + location. Args: - map_fun (function): Function which, given the qubit id, returns - an integer describing the physical location (must be constant). + map_fun (function): Function which, given the qubit id, returns an integer describing the physical + location (must be constant). """ - BasicMapperEngine.__init__(self) + super().__init__() self.map = map_fun self.current_mapping = dict() def receive(self, command_list): """ - Receives a command list and passes it to the next engine, adding - qubit placement tags to allocate gates. + Receives a command list and passes it to the next engine, adding qubit placement tags to allocate gates. Args: - command_list (list of Command objects): list of commands to - receive. + command_list (list of Command objects): list of commands to receive. """ for cmd in command_list: ids = [qb.id for qr in cmd.qubits for qb in qr] ids += [qb.id for qb in cmd.control_qubits] - for ID in ids: - if ID not in self.current_mapping: - self._current_mapping[ID] = self.map(ID) + for qubit_id in ids: + if qubit_id not in self.current_mapping: + self._current_mapping[qubit_id] = self.map(qubit_id) self._send_cmd_with_mapped_ids(cmd) diff --git a/projectq/cengines/_optimize.py b/projectq/cengines/_optimize.py index cd9c9b7b0..39762bd6a 100755 --- a/projectq/cengines/_optimize.py +++ b/projectq/cengines/_optimize.py @@ -16,60 +16,68 @@ Contains a local optimizer engine. """ -from projectq.cengines import BasicEngine +import warnings + from projectq.ops import FlushGate, FastForwardingGate, NotMergeable +from ._basics import BasicEngine + class LocalOptimizer(BasicEngine): """ - LocalOptimizer is a compiler engine which optimizes locally (merging - rotations, cancelling gates with their inverse) in a local window of user- - defined size. - - It stores all commands in a dict of lists, where each qubit has its own - gate pipeline. After adding a gate, it tries to merge / cancel successive - gates using the get_merged and get_inverse functions of the gate (if - available). For examples, see BasicRotationGate. Once a list corresponding - to a qubit contains >=m gates, the pipeline is sent on to the next engine. + LocalOptimizer is a compiler engine which optimizes locally (merging rotations, cancelling gates with their + inverse) in a local window of user- defined size. + + It stores all commands in a dict of lists, where each qubit has its own gate pipeline. After adding a gate, it + tries to merge / cancel successive gates using the get_merged and get_inverse functions of the gate (if + available). For examples, see BasicRotationGate. Once a list corresponding to a qubit contains >=m gates, the + pipeline is sent on to the next engine. """ - def __init__(self, m=5): + def __init__(self, cache_size=5, m=None): # pylint: disable=invalid-name """ Initialize a LocalOptimizer object. Args: - m (int): Number of gates to cache per qubit, before sending on the - first gate. + cache_size (int): Number of gates to cache per qubit, before sending on the first gate. """ - BasicEngine.__init__(self) + super().__init__() self._l = dict() # dict of lists containing operations for each qubit - self._m = m # wait for m gates before sending on + + if m: + warnings.warn( + 'Pending breaking API change: LocalOptimizer(m=5) will be dropped in a future version in favor of ' + 'LinearMapper(cache_size=5)', + DeprecationWarning, + ) + cache_size = m + self._cache_size = cache_size # wait for m gates before sending on # sends n gate operations of the qubit with index idx - def _send_qubit_pipeline(self, idx, n): + def _send_qubit_pipeline(self, idx, n_gates): """ Send n gate operations of the qubit with index idx to the next engine. """ - il = self._l[idx] # temporary label for readability - for i in range(min(n, len(il))): # loop over first n operations + il = self._l[idx] # pylint: disable=invalid-name + for i in range(min(n_gates, len(il))): # loop over first n operations # send all gates before n-qubit gate for other qubits involved # --> recursively call send_helper other_involved_qubits = [qb for qreg in il[i].all_qubits for qb in qreg if qb.id != idx] for qb in other_involved_qubits: - Id = qb.id + qubit_id = qb.id try: gateloc = 0 # find location of this gate within its list - while self._l[Id][gateloc] != il[i]: + while self._l[qubit_id][gateloc] != il[i]: gateloc += 1 - gateloc = self._optimize(Id, gateloc) + gateloc = self._optimize(qubit_id, gateloc) # flush the gates before the n-qubit gate - self._send_qubit_pipeline(Id, gateloc) + self._send_qubit_pipeline(qubit_id, gateloc) # delete the n-qubit gate, we're taking care of it # and don't want the other qubit to do so - self._l[Id] = self._l[Id][1:] + self._l[qubit_id] = self._l[qubit_id][1:] except IndexError: # pragma: no cover print("Invalid qubit pipeline encountered (in the process of shutting down?).") @@ -77,19 +85,19 @@ def _send_qubit_pipeline(self, idx, n): # --> send on the n-qubit gate self.send([il[i]]) # n operations have been sent on --> resize our gate list - self._l[idx] = self._l[idx][n:] + self._l[idx] = self._l[idx][n_gates:] - def _get_gate_indices(self, idx, i, IDs): + def _get_gate_indices(self, idx, i, qubit_ids): """ - Return all indices of a command, each index corresponding to the - command's index in one of the qubits' command lists. + Return all indices of a command, each index corresponding to the command's index in one of the qubits' command + lists. Args: idx (int): qubit index i (int): command position in qubit idx's command list IDs (list): IDs of all qubits involved in the command """ - N = len(IDs) + N = len(qubit_ids) # 1-qubit gate: only gate at index i in list #idx is involved if N == 1: return [i] @@ -101,8 +109,8 @@ def _get_gate_indices(self, idx, i, IDs): cmd = self._l[idx][i] num_identical_to_skip = sum(1 for prev_cmd in self._l[idx][:i] if prev_cmd == cmd) indices = [] - for Id in IDs: - identical_indices = [i for i, c in enumerate(self._l[Id]) if c == cmd] + for qubit_id in qubit_ids: + identical_indices = [i for i, c in enumerate(self._l[qubit_id]) if c == cmd] indices.append(identical_indices[num_identical_to_skip]) return indices @@ -125,12 +133,11 @@ 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] gid = self._get_gate_indices(idx, i, qubitids) - for j in range(len(qubitids)): + for j, qubit_id in enumerate(qubitids): new_list = ( - self._l[qubitids[j]][0 : gid[j]] # noqa: E203 - + self._l[qubitids[j]][gid[j] + 1 :] # noqa: E203 + self._l[qubit_id][0 : gid[j]] + self._l[qubit_id][gid[j] + 1 :] # noqa: E203 # noqa: E203 ) - self._l[qubitids[j]] = new_list + self._l[qubitids[j]] = new_list # pylint: disable=undefined-loop-variable i = 0 limit -= 1 continue @@ -145,17 +152,16 @@ def _optimize(self, idx, lim=None): # check that there are no other gates between this and its # inverse on any of the other qubits involved erase = True - for j in range(len(qubitids)): - erase *= inv == self._l[qubitids[j]][gid[j] + 1] + for j, qubit_id in enumerate(qubitids): + erase *= inv == self._l[qubit_id][gid[j] + 1] # drop these two gates if possible and goto next iteration if erase: - for j in range(len(qubitids)): + for j, qubit_id in enumerate(qubitids): new_list = ( - self._l[qubitids[j]][0 : gid[j]] # noqa: E203 - + self._l[qubitids[j]][gid[j] + 2 :] # noqa: E203 + self._l[qubit_id][0 : gid[j]] + self._l[qubit_id][gid[j] + 2 :] # noqa: E203 # noqa: E203 ) - self._l[qubitids[j]] = new_list + self._l[qubit_id] = new_list i = 0 limit -= 2 continue @@ -169,18 +175,18 @@ def _optimize(self, idx, lim=None): gid = self._get_gate_indices(idx, i, qubitids) merge = True - for j in range(len(qubitids)): - m = self._l[qubitids[j]][gid[j]].get_merged(self._l[qubitids[j]][gid[j] + 1]) - merge *= m == merged_command + for j, qubit_id in enumerate(qubitids): + merged = self._l[qubit_id][gid[j]].get_merged(self._l[qubit_id][gid[j] + 1]) + merge *= merged == merged_command if merge: - for j in range(len(qubitids)): - self._l[qubitids[j]][gid[j]] = merged_command + for j, qubit_id in enumerate(qubitids): + self._l[qubit_id][gid[j]] = merged_command new_list = ( - self._l[qubitids[j]][0 : gid[j] + 1] # noqa: E203 - + self._l[qubitids[j]][gid[j] + 2 :] # noqa: E203 + self._l[qubit_id][0 : gid[j] + 1] # noqa: E203 + + self._l[qubit_id][gid[j] + 2 :] # noqa: E203 ) - self._l[qubitids[j]] = new_list + self._l[qubit_id] = new_list i = 0 limit -= 1 continue @@ -197,13 +203,13 @@ def _check_and_send(self): """ for i in self._l: if ( - len(self._l[i]) >= self._m + len(self._l[i]) >= self._cache_size or len(self._l[i]) > 0 and isinstance(self._l[i][-1].gate, FastForwardingGate) ): self._optimize(i) - if len(self._l[i]) >= self._m and not isinstance(self._l[i][-1].gate, FastForwardingGate): - self._send_qubit_pipeline(i, len(self._l[i]) - self._m + 1) + if len(self._l[i]) >= self._cache_size and not isinstance(self._l[i][-1].gate, FastForwardingGate): + self._send_qubit_pipeline(i, len(self._l[i]) - self._cache_size + 1) elif len(self._l[i]) > 0 and isinstance(self._l[i][-1].gate, FastForwardingGate): self._send_qubit_pipeline(i, len(self._l[i])) new_dict = dict() @@ -221,10 +227,10 @@ def _cache_cmd(self, cmd): idlist = [qubit.id for sublist in cmd.all_qubits for qubit in sublist] # add gate command to each of the qubits involved - for ID in idlist: - if ID not in self._l: - self._l[ID] = [] - self._l[ID] += [cmd] + for qubit_id in idlist: + if qubit_id not in self._l: + self._l[qubit_id] = [] + self._l[qubit_id] += [cmd] self._check_and_send() @@ -240,10 +246,11 @@ def receive(self, command_list): self._send_qubit_pipeline(idx, len(self._l[idx])) new_dict = dict() for idx in self._l: - if len(self._l[idx]) > 0: + if len(self._l[idx]) > 0: # pragma: no cover new_dict[idx] = self._l[idx] self._l = new_dict - assert self._l == dict() + if self._l != dict(): # pragma: no cover + raise RuntimeError('Internal compiler error: qubits remaining in LocalOptimizer after a flush!') self.send([cmd]) else: self._cache_cmd(cmd) diff --git a/projectq/cengines/_optimize_test.py b/projectq/cengines/_optimize_test.py index f4cc651ff..fc2ac96c0 100755 --- a/projectq/cengines/_optimize_test.py +++ b/projectq/cengines/_optimize_test.py @@ -15,6 +15,9 @@ """Tests for projectq.cengines._optimize.py.""" import math + +import pytest + from projectq import MainEngine from projectq.cengines import DummyEngine from projectq.ops import ( @@ -31,8 +34,20 @@ from projectq.cengines import _optimize +def test_local_optimizer_init_api_change(): + with pytest.warns(DeprecationWarning): + tmp = _optimize.LocalOptimizer(m=10) + assert tmp._cache_size == 10 + + local_optimizer = _optimize.LocalOptimizer() + assert local_optimizer._cache_size == 5 + + local_optimizer = _optimize.LocalOptimizer(cache_size=10) + assert local_optimizer._cache_size == 10 + + def test_local_optimizer_caching(): - local_optimizer = _optimize.LocalOptimizer(m=4) + local_optimizer = _optimize.LocalOptimizer(cache_size=4) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) # Test that it caches for each qubit 3 gates @@ -63,7 +78,7 @@ def test_local_optimizer_caching(): def test_local_optimizer_flush_gate(): - local_optimizer = _optimize.LocalOptimizer(m=4) + local_optimizer = _optimize.LocalOptimizer(cache_size=4) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) # Test that it caches for each qubit 3 gates @@ -78,7 +93,7 @@ def test_local_optimizer_flush_gate(): def test_local_optimizer_fast_forwarding_gate(): - local_optimizer = _optimize.LocalOptimizer(m=4) + local_optimizer = _optimize.LocalOptimizer(cache_size=4) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) # Test that FastForwardingGate (e.g. Deallocate) flushes that qb0 pipeline @@ -93,7 +108,7 @@ def test_local_optimizer_fast_forwarding_gate(): def test_local_optimizer_cancel_inverse(): - local_optimizer = _optimize.LocalOptimizer(m=4) + local_optimizer = _optimize.LocalOptimizer(cache_size=4) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) # Test that it cancels inverses (H, CNOT are self-inverse) @@ -121,7 +136,7 @@ def test_local_optimizer_cancel_inverse(): def test_local_optimizer_mergeable_gates(): - local_optimizer = _optimize.LocalOptimizer(m=4) + local_optimizer = _optimize.LocalOptimizer(cache_size=4) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) # Test that it merges mergeable gates such as Rx @@ -136,7 +151,7 @@ def test_local_optimizer_mergeable_gates(): def test_local_optimizer_identity_gates(): - local_optimizer = _optimize.LocalOptimizer(m=4) + local_optimizer = _optimize.LocalOptimizer(cache_size=4) backend = DummyEngine(save_commands=True) eng = MainEngine(backend=backend, engine_list=[local_optimizer]) # Test that it merges mergeable gates such as Rx diff --git a/projectq/cengines/_replacer/_decomposition_rule.py b/projectq/cengines/_replacer/_decomposition_rule.py index b53016eed..f7bee3d67 100755 --- a/projectq/cengines/_replacer/_decomposition_rule.py +++ b/projectq/cengines/_replacer/_decomposition_rule.py @@ -13,14 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Module containing the definition of a decomposition rule""" + from projectq.ops import BasicGate class ThisIsNotAGateClassError(TypeError): - pass + """Exception raised when a gate instance is encountered instead of a gate class in a decomposition rule""" -class DecompositionRule: +class DecompositionRule: # pylint: disable=too-few-public-methods """ A rule for breaking down specific gates into sequences of simpler gates. """ diff --git a/projectq/cengines/_replacer/_decomposition_rule_set.py b/projectq/cengines/_replacer/_decomposition_rule_set.py index 45c004e6c..aba4eba6b 100755 --- a/projectq/cengines/_replacer/_decomposition_rule_set.py +++ b/projectq/cengines/_replacer/_decomposition_rule_set.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Module containing the definition of a decomposition rule set""" + from projectq.meta import Dagger @@ -40,6 +42,9 @@ def __init__(self, rules=None, modules=None): ) def add_decomposition_rules(self, rules): + """ + Add some decomposition rules to a decomposition rule set + """ for rule in rules: self.add_decomposition_rule(rule) @@ -57,7 +62,7 @@ def add_decomposition_rule(self, rule): self.decompositions[cls].append(decomp_obj) -class ModuleWithDecompositionRuleSet: # pragma: no cover +class ModuleWithDecompositionRuleSet: # pragma: no cover # pylint: disable=too-few-public-methods """ Interface type for explaining one of the parameters that can be given to DecompositionRuleSet. @@ -72,7 +77,7 @@ def __init__(self, all_defined_decomposition_rules): self.all_defined_decomposition_rules = all_defined_decomposition_rules -class _Decomposition(object): +class _Decomposition: # pylint: disable=too-few-public-methods """ The Decomposition class can be used to register a decomposition rule (by calling register_decomposition) diff --git a/projectq/cengines/_replacer/_replacer.py b/projectq/cengines/_replacer/_replacer.py index 07ea5d3fb..0a9a4d684 100755 --- a/projectq/cengines/_replacer/_replacer.py +++ b/projectq/cengines/_replacer/_replacer.py @@ -26,34 +26,32 @@ class NoGateDecompositionError(Exception): - pass + """Exception raised when no gate decomposition rule can be found""" class InstructionFilter(BasicEngine): """ - The InstructionFilter is a compiler engine which changes the behavior of - is_available according to a filter function. All commands are passed to - this function, which then returns whether this command can be executed - (True) or needs replacement (False). + The InstructionFilter is a compiler engine which changes the behavior of is_available according to a filter + function. All commands are passed to this function, which then returns whether this command can be executed (True) + or needs replacement (False). """ def __init__(self, filterfun): """ - Initializer: The provided filterfun returns True for all commands - which do not need replacement and False for commands that do. + Initializer: The provided filterfun returns True for all commands which do not need replacement and False for + commands that do. Args: - filterfun (function): Filter function which returns True for - available commands, and False otherwise. filterfun will be - called as filterfun(self, cmd). + filterfun (function): Filter function which returns True for available commands, and False + otherwise. filterfun will be called as filterfun(self, cmd). """ BasicEngine.__init__(self) self._filterfun = filterfun def is_available(self, cmd): """ - Specialized implementation of BasicBackend.is_available: Forwards this - call to the filter function given to the constructor. + Specialized implementation of BasicBackend.is_available: Forwards this call to the filter function given to + the constructor. Args: cmd (Command): Command for which to check availability. @@ -72,15 +70,14 @@ def receive(self, command_list): class AutoReplacer(BasicEngine): """ - The AutoReplacer is a compiler engine which uses engine.is_available in - order to determine which commands need to be replaced/decomposed/compiled - further. The loaded setup is used to find decomposition rules appropriate - for each command (e.g., setups.default). + The AutoReplacer is a compiler engine which uses engine.is_available in order to determine which commands need to + be replaced/decomposed/compiled further. The loaded setup is used to find decomposition rules appropriate for each + command (e.g., setups.default). """ def __init__( self, - decompositionRuleSet, + decomposition_rule_se, decomposition_chooser=lambda cmd, decomposition_list: decomposition_list[0], ): """ @@ -109,13 +106,12 @@ def decomposition_chooser(cmd, decomp_list): """ BasicEngine.__init__(self) self._decomp_chooser = decomposition_chooser - self.decompositionRuleSet = decompositionRuleSet + self.decomposition_rule_set = decomposition_rule_se - def _process_command(self, cmd): + def _process_command(self, cmd): # pylint: disable=too-many-locals,too-many-branches """ - Check whether a command cmd can be handled by further engines and, - if not, replace it using the decomposition rules loaded with the setup - (e.g., setups.default). + Check whether a command cmd can be handled by further engines and, if not, replace it using the decomposition + rules loaded with the setup (e.g., setups.default). Args: cmd (Command): Command to process. @@ -123,7 +119,7 @@ def _process_command(self, cmd): Raises: Exception if no replacement is available in the loaded setup. """ - if self.is_available(cmd): + if self.is_available(cmd): # pylint: disable=too-many-nested-blocks self.send([cmd]) else: # First check for a decomposition rules of the gate class, then @@ -133,7 +129,7 @@ def _process_command(self, cmd): # If gate does not have an inverse it's parent classes are # DaggeredGate, BasicGate, object. Hence don't check the last two inverse_mro = type(get_inverse(cmd.gate)).mro()[:-2] - rules = self.decompositionRuleSet.decompositions + rules = self.decomposition_rule_set.decompositions # If the decomposition rule to remove negatively controlled qubits is present in the list of potential # decompositions, we process it immediately, before any other decompositions. @@ -152,13 +148,13 @@ def _process_command(self, cmd): if level < len(gate_mro): class_name = gate_mro[level].__name__ try: - potential_decomps = [d for d in rules[class_name]] + potential_decomps = rules[class_name] except KeyError: pass # throw out the ones which don't recognize the command - for d in potential_decomps: - if d.check(cmd): - decomp_list.append(d) + for decomp in potential_decomps: + if decomp.check(cmd): + decomp_list.append(decomp) if len(decomp_list) != 0: break # Check for rules implementing the inverse gate @@ -170,9 +166,9 @@ def _process_command(self, cmd): except KeyError: pass # throw out the ones which don't recognize the command - for d in potential_decomps: - if d.check(cmd): - decomp_list.append(d) + for decomp in potential_decomps: + if decomp.check(cmd): + decomp_list.append(decomp) if len(decomp_list) != 0: break diff --git a/projectq/cengines/_swapandcnotflipper.py b/projectq/cengines/_swapandcnotflipper.py index 481dd7365..dfb87b7a3 100755 --- a/projectq/cengines/_swapandcnotflipper.py +++ b/projectq/cengines/_swapandcnotflipper.py @@ -19,10 +19,12 @@ """ from copy import deepcopy -from projectq.cengines import BasicEngine, ForwarderEngine, CommandModifier from projectq.meta import get_control_count from projectq.ops import All, NOT, CNOT, H, Swap +from ._basics import BasicEngine, ForwarderEngine +from ._cmdmodifier import CommandModifier + class SwapAndCNOTFlipper(BasicEngine): """ @@ -46,7 +48,7 @@ def __init__(self, connectivity): the physical ids (c, t) with c being the control and t being the target qubit. """ - BasicEngine.__init__(self) + super().__init__() self.connectivity = connectivity def is_available(self, cmd): @@ -59,7 +61,7 @@ def is_available(self, cmd): """ return self._is_swap(cmd) or self.next_engine.is_available(cmd) - def _is_cnot(self, cmd): + def _is_cnot(self, cmd): # pylint: disable=no-self-use """ Check if the command corresponds to a CNOT (controlled NOT gate). @@ -68,7 +70,7 @@ def _is_cnot(self, cmd): """ return isinstance(cmd.gate, NOT.__class__) and get_control_count(cmd) == 1 - def _is_swap(self, cmd): + def _is_swap(self, cmd): # pylint: disable=no-self-use """ Check if the command corresponds to a Swap gate. @@ -133,7 +135,8 @@ def receive(self, command_list): elif self._is_swap(cmd): qubits = [qb for qr in cmd.qubits for qb in qr] ids = [qb.id for qb in qubits] - assert len(ids) == 2 + if len(ids) != 2: + raise RuntimeError('Swap gate is a 2-qubit gate!') if tuple(ids) in self.connectivity: control = [qubits[0]] target = [qubits[1]] diff --git a/projectq/cengines/_swapandcnotflipper_test.py b/projectq/cengines/_swapandcnotflipper_test.py index fdea4fdd7..61256aa52 100755 --- a/projectq/cengines/_swapandcnotflipper_test.py +++ b/projectq/cengines/_swapandcnotflipper_test.py @@ -18,9 +18,11 @@ from projectq import MainEngine from projectq.cengines import DummyEngine -from projectq.ops import All, H, CNOT, X, Swap +from projectq.ops import All, H, CNOT, X, Swap, Command from projectq.meta import Control, Compute, Uncompute, ComputeTag, UncomputeTag -from projectq.cengines import _swapandcnotflipper +from projectq.types import WeakQubitRef + +from . import _swapandcnotflipper def test_swapandcnotflipper_missing_connection(): @@ -31,6 +33,16 @@ def test_swapandcnotflipper_missing_connection(): Swap | (qubit1, qubit2) +def test_swapandcnotflipper_invalid_swap(): + flipper = _swapandcnotflipper.SwapAndCNOTFlipper(set()) + + qb0 = WeakQubitRef(engine=None, idx=0) + qb1 = WeakQubitRef(engine=None, idx=1) + qb2 = WeakQubitRef(engine=None, idx=2) + with pytest.raises(RuntimeError): + flipper.receive([Command(engine=None, gate=Swap, qubits=([qb0, qb1], [qb2]))]) + + def test_swapandcnotflipper_is_available(): flipper = _swapandcnotflipper.SwapAndCNOTFlipper(set()) dummy = DummyEngine() diff --git a/projectq/cengines/_tagremover.py b/projectq/cengines/_tagremover.py index 213f2ed6c..60da9b0d8 100755 --- a/projectq/cengines/_tagremover.py +++ b/projectq/cengines/_tagremover.py @@ -13,25 +13,24 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Contains a TagRemover engine, which removes temporary command tags (such as -Compute/Uncompute), thus enabling optimization across meta statements (loops -after unrolling, compute/uncompute, ...) +Contains a TagRemover engine, which removes temporary command tags (such as Compute/Uncompute), thus enabling +optimization across meta statements (loops after unrolling, compute/uncompute, ...) """ -from projectq.cengines import BasicEngine from projectq.meta import ComputeTag, UncomputeTag +from ._basics import BasicEngine + class TagRemover(BasicEngine): """ - TagRemover is a compiler engine which removes temporary command tags (see - the tag classes such as LoopTag in projectq.meta._loop). + TagRemover is a compiler engine which removes temporary command tags (see the tag classes such as LoopTag in + projectq.meta._loop). - Removing tags is important (after having handled them if necessary) in - order to enable optimizations across meta-function boundaries (compute/ - action/uncompute or loops after unrolling) + Removing tags is important (after having handled them if necessary) in order to enable optimizations across + meta-function boundaries (compute/ action/uncompute or loops after unrolling) """ - def __init__(self, tags=[ComputeTag, UncomputeTag]): + def __init__(self, tags=None): """ Construct the TagRemover. @@ -39,19 +38,21 @@ def __init__(self, tags=[ComputeTag, UncomputeTag]): tags: A list of meta tag classes (e.g., [ComputeTag, UncomputeTag]) denoting the tags to remove """ - BasicEngine.__init__(self) - assert isinstance(tags, list) - self._tags = tags + super().__init__() + if not tags: + self._tags = [ComputeTag, UncomputeTag] + elif isinstance(tags, list): + self._tags = tags + else: + raise TypeError('tags should be a list! Got: {}'.format(tags)) def receive(self, command_list): """ - Receive a list of commands from the previous engine, remove all tags - which are an instance of at least one of the meta tags provided in the - constructor, and then send them on to the next compiler engine. + Receive a list of commands from the previous engine, remove all tags which are an instance of at least one of + the meta tags provided in the constructor, and then send them on to the next compiler engine. Args: - command_list (list): List of commands to receive and then - (after removing tags) send on. + command_list (list): List of commands to receive and then (after removing tags) send on. """ for cmd in command_list: for tag in self._tags: diff --git a/projectq/cengines/_tagremover_test.py b/projectq/cengines/_tagremover_test.py index 9f84fce7e..d22369762 100755 --- a/projectq/cengines/_tagremover_test.py +++ b/projectq/cengines/_tagremover_test.py @@ -14,6 +14,8 @@ # limitations under the License. """Tests for projectq.cengines._tagremover.py.""" +import pytest + from projectq import MainEngine from projectq.meta import ComputeTag, UncomputeTag from projectq.ops import Command, H @@ -27,6 +29,11 @@ def test_tagremover_default(): assert tag_remover._tags == [ComputeTag, UncomputeTag] +def test_tagremover_invalid(): + with pytest.raises(TypeError): + _tagremover.TagRemover(ComputeTag) + + def test_tagremover(): backend = DummyEngine(save_commands=True) tag_remover = _tagremover.TagRemover([type("")]) diff --git a/projectq/cengines/_testengine.py b/projectq/cengines/_testengine.py index 106cee637..eace5744b 100755 --- a/projectq/cengines/_testengine.py +++ b/projectq/cengines/_testengine.py @@ -15,25 +15,34 @@ """TestEngine and DummyEngine.""" from copy import deepcopy -from projectq.cengines import BasicEngine from projectq.ops import FlushGate +from ._basics import BasicEngine + + +def _compare_cmds(cmd1, cmd2): + """Compare two command objects""" + cmd2 = deepcopy(cmd2) + cmd2.engine = cmd1.engine + return cmd1 == cmd2 + class CompareEngine(BasicEngine): """ - CompareEngine is an engine which saves all commands. It is only intended - for testing purposes. Two CompareEngine backends can be compared and - return True if they contain the same commmands. + CompareEngine is an engine which saves all commands. It is only intended for testing purposes. Two CompareEngine + backends can be compared and return True if they contain the same commmands. """ def __init__(self): - BasicEngine.__init__(self) + super().__init__() self._l = [[]] def is_available(self, cmd): + """All commands are accepted by this compiler engine""" return True def cache_cmd(self, cmd): + """Cache a command""" # are there qubit ids that haven't been added to the list? all_qubit_id_list = [qubit.id for qureg in cmd.all_qubits for qubit in qureg] maxidx = int(0) @@ -43,7 +52,7 @@ def cache_cmd(self, cmd): # if so, increase size of list to account for these qubits add = maxidx + 1 - len(self._l) if add > 0: - for i in range(add): + for _ in range(add): self._l += [[]] # add gate command to each of the qubits involved @@ -51,17 +60,19 @@ def cache_cmd(self, cmd): self._l[qubit_id] += [cmd] def receive(self, command_list): + """ + Receives a command list and, for each command, stores it inside the cache before sending it to the next + compiler engine. + + Args: + command_list (list of Command objects): list of commands to receive. + """ for cmd in command_list: if not cmd.gate == FlushGate(): self.cache_cmd(cmd) if not self.is_last_engine: self.send(command_list) - def compare_cmds(self, c1, c2): - c2 = deepcopy(c2) - c2.engine = c1.engine - return c1 == c2 - def __eq__(self, other): if not isinstance(other, CompareEngine) or len(self._l) != len(other._l): return False @@ -69,7 +80,7 @@ def __eq__(self, other): if len(self._l[i]) != len(other._l[i]): return False for j in range(len(self._l[i])): - if not self.compare_cmds(self._l[i][j], other._l[i][j]): + if not _compare_cmds(self._l[i][j], other._l[i][j]): return False return True @@ -90,11 +101,10 @@ class DummyEngine(BasicEngine): """ DummyEngine used for testing. - The DummyEngine forwards all commands directly to next engine. - If self.is_last_engine == True it just discards all gates. - By setting save_commands == True all commands get saved as a - list in self.received_commands. Elements are appended to this - list so they are ordered according to when they are received. + The DummyEngine forwards all commands directly to next engine. If self.is_last_engine == True it just discards + all gates. + By setting save_commands == True all commands get saved as a list in self.received_commands. Elements are appended + to this list so they are ordered according to when they are received. """ def __init__(self, save_commands=False): @@ -105,17 +115,23 @@ def __init__(self, save_commands=False): save_commands (default = False): If True, commands are saved in self.received_commands. """ - BasicEngine.__init__(self) + super().__init__() self.save_commands = save_commands self.received_commands = [] def is_available(self, cmd): + """All commands are accepted by this compiler engine""" return True def receive(self, command_list): + """ + Receives a command list and, for each command, stores it internally if requested before sending it to the next + compiler engine. + + Args: + command_list (list of Command objects): list of commands to receive. + """ if self.save_commands: self.received_commands.extend(command_list) if not self.is_last_engine: self.send(command_list) - else: - pass diff --git a/projectq/cengines/_twodmapper.py b/projectq/cengines/_twodmapper.py index 2c068c4ad..f3bf4b5d3 100644 --- a/projectq/cengines/_twodmapper.py +++ b/projectq/cengines/_twodmapper.py @@ -15,12 +15,10 @@ """ Mapper for a quantum circuit to a 2D square grid. -Input: Quantum circuit with 1 and 2 qubit gates on n qubits. Gates are assumed - to be applied in parallel if they act on disjoint qubit(s) and any pair - of qubits can perform a 2 qubit gate (all-to-all connectivity) -Output: Quantum circuit in which qubits are placed in 2-D square grid in which - only nearest neighbour qubits can perform a 2 qubit gate. The mapper - uses Swap gates in order to move qubits next to each other. +Input: Quantum circuit with 1 and 2 qubit gates on n qubits. Gates are assumed to be applied in parallel if they act + on disjoint qubit(s) and any pair of qubits can perform a 2 qubit gate (all-to-all connectivity) +Output: Quantum circuit in which qubits are placed in 2-D square grid in which only nearest neighbour qubits can + perform a 2 qubit gate. The mapper uses Swap gates in order to move qubits next to each other. """ from copy import deepcopy import itertools @@ -29,7 +27,6 @@ import networkx as nx -from projectq.cengines import BasicMapperEngine, LinearMapper, return_swap_depth from projectq.meta import LogicalQubitIDTag from projectq.ops import ( AllocateQubitGate, @@ -41,12 +38,15 @@ from projectq.types import WeakQubitRef -class GridMapper(BasicMapperEngine): +from ._basicmapper import BasicMapperEngine +from ._linearmapper import LinearMapper, return_swap_depth + + +class GridMapper(BasicMapperEngine): # pylint: disable=too-many-instance-attributes """ Mapper to a 2-D grid graph. - Mapped qubits on the grid are numbered in row-major order. E.g. for - 3 rows and 2 columns: + Mapped qubits on the grid are numbered in row-major order. E.g. for 3 rows and 2 columns: 0 - 1 | | @@ -54,39 +54,33 @@ class GridMapper(BasicMapperEngine): | | 4 - 5 - The numbers are the mapped qubit ids. The backend might number - the qubits on the grid differently (e.g. not row-major), we call these - backend qubit ids. If the backend qubit ids are not row-major, one can - pass a dictionary translating from our row-major mapped ids to these - backend ids. + The numbers are the mapped qubit ids. The backend might number the qubits on the grid differently (e.g. not + row-major), we call these backend qubit ids. If the backend qubit ids are not row-major, one can pass a dictionary + translating from our row-major mapped ids to these backend ids. - Note: The algorithm sorts twice inside each column and once inside each - row. + Note: The algorithm sorts twice inside each column and once inside each row. Attributes: - current_mapping: Stores the mapping: key is logical qubit id, value - is backend qubit id. + current_mapping: Stores the mapping: key is logical qubit id, value is backend qubit id. storage(int): Number of gate it caches before mapping. num_rows(int): Number of rows in the grid num_columns(int): Number of columns in the grid num_qubits(int): num_rows x num_columns = number of qubits num_mappings (int): Number of times the mapper changed the mapping - depth_of_swaps (dict): Key are circuit depth of swaps, value is the - number of such mappings which have been + depth_of_swaps (dict): Key are circuit depth of swaps, value is the number of such mappings which have been applied - num_of_swaps_per_mapping (dict): Key are the number of swaps per - mapping, value is the number of such - mappings which have been applied + num_of_swaps_per_mapping (dict): Key are the number of swaps per mapping, value is the number of such mappings + which have been applied """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, num_rows, num_columns, mapped_ids_to_backend_ids=None, storage=1000, - optimization_function=lambda x: return_swap_depth(x), + optimization_function=return_swap_depth, num_optimization_steps=50, ): """ @@ -95,27 +89,19 @@ def __init__( Args: num_rows(int): Number of rows in the grid num_columns(int): Number of columns in the grid. - mapped_ids_to_backend_ids(dict): Stores a mapping from mapped ids - which are 0,...,self.num_qubits-1 - in row-major order on the grid to - the corresponding qubit ids of the - backend. Key: mapped id. Value: - corresponding backend id. - Default is None which means - backend ids are identical to - mapped ids. + mapped_ids_to_backend_ids(dict): Stores a mapping from mapped ids which are 0,...,self.num_qubits-1 in + row-major order on the grid to the corresponding qubit ids of the + backend. Key: mapped id. Value: corresponding backend id. Default is + None which means backend ids are identical to mapped ids. storage: Number of gates to temporarily store - optimization_function: Function which takes a list of swaps and - returns a cost value. Mapper chooses a - permutation which minimizes this cost. - Default optimizes for circuit depth. - num_optimization_steps(int): Number of different permutations to - of the matching to try and minimize - the cost. + optimization_function: Function which takes a list of swaps and returns a cost value. Mapper chooses a + permutation which minimizes this cost. Default optimizes for circuit depth. + num_optimization_steps(int): Number of different permutations to of the matching to try and minimize the + cost. Raises: RuntimeError: if incorrect `mapped_ids_to_backend_ids` parameter """ - BasicMapperEngine.__init__(self) + super().__init__() self.num_rows = num_rows self.num_columns = num_columns self.num_qubits = num_rows * num_columns @@ -193,25 +179,19 @@ def is_available(self, cmd): num_qubits = 0 for qureg in cmd.all_qubits: num_qubits += len(qureg) - if num_qubits <= 2: - return True - else: - return False + return num_qubits <= 2 def _return_new_mapping(self): """ Returns a new mapping of the qubits. - It goes through self._saved_commands and tries to find a - mapping to apply these gates on a first come first served basis. - It reuses the function of a 1D mapper and creates a mapping for a - 1D linear chain and then wraps it like a snake onto the square grid. + It goes through self._saved_commands and tries to find a mapping to apply these gates on a first come first + served basis. It reuses the function of a 1D mapper and creates a mapping for a 1D linear chain and then + wraps it like a snake onto the square grid. - One might create better mappings by specializing this function for a - square grid. + One might create better mappings by specializing this function for a square grid. - Returns: A new mapping as a dict. key is logical qubit id, - value is mapped id + Returns: A new mapping as a dict. key is logical qubit id, value is mapped id """ # Change old mapping to 1D in order to use LinearChain heuristic if self._current_row_major_mapping: @@ -236,8 +216,7 @@ def _return_new_mapping(self): def _compare_and_swap(self, element0, element1, key): """ - If swapped (inplace), then return swap operation - so that key(element0) < key(element1) + If swapped (inplace), then return swap operation so that key(element0) < key(element1) """ if key(element0) > key(element1): mapped_id0 = element0.current_column + element0.current_row * self.num_columns @@ -254,8 +233,7 @@ def _compare_and_swap(self, element0, element1, key): element1.final_column = tmp_1 element1.row_after_step_1 = tmp_2 return swap_operation - else: - return None + return None def _sort_within_rows(self, final_positions, key): swap_operations = [] @@ -301,30 +279,29 @@ def _sort_within_columns(self, final_positions, key): swap_operations.append(swap) return swap_operations - def return_swaps(self, old_mapping, new_mapping, permutation=None): + def return_swaps( # pylint: disable=too-many-locals,too-many-branches,too-many-statements + self, old_mapping, new_mapping, permutation=None + ): """ Returns the swap operation to change mapping Args: - old_mapping: dict: keys are logical ids and values are mapped - qubit ids - new_mapping: dict: keys are logical ids and values are mapped - qubit ids - permutation: list of int from 0, 1, ..., self.num_rows-1. It is - used to permute the found perfect matchings. Default - is None which keeps the original order. + old_mapping: dict: keys are logical ids and values are mapped qubit ids + new_mapping: dict: keys are logical ids and values are mapped qubit ids + permutation: list of int from 0, 1, ..., self.num_rows-1. It is used to permute the found perfect + matchings. Default is None which keeps the original order. Returns: - List of tuples. Each tuple is a swap operation which needs to be - applied. Tuple contains the two mapped qubit ids for the Swap. + List of tuples. Each tuple is a swap operation which needs to be applied. Tuple contains the two mapped + qubit ids for the Swap. """ if permutation is None: permutation = list(range(self.num_rows)) swap_operations = [] - class Position(object): + class Position: # pylint: disable=too-few-public-methods """Custom Container.""" - def __init__( + def __init__( # pylint: disable=too-many-arguments self, current_row, current_column, @@ -375,20 +352,19 @@ def __init__( final_column=new_column, ) final_positions[row][column] = info_container - assert len(not_used_mapped_ids) == 0 + if len(not_used_mapped_ids) > 0: # pragma: no cover + raise RuntimeError('Internal compiler error: len(not_used_mapped_ids) > 0') # 1. Assign column_after_step_1 for each element # Matching contains the num_columns matchings matchings = [None for i in range(self.num_rows)] - # Build bipartite graph. Nodes are the current columns numbered - # (0, 1, ...) and the destination columns numbered with an offset of - # self.num_columns (0 + offset, 1+offset, ...) + # Build bipartite graph. Nodes are the current columns numbered (0, 1, ...) and the destination columns + # numbered with an offset of self.num_columns (0 + offset, 1+offset, ...) graph = nx.Graph() offset = self.num_columns graph.add_nodes_from(range(self.num_columns), bipartite=0) graph.add_nodes_from(range(offset, offset + self.num_columns), bipartite=1) - # Add an edge to the graph from (i, j+offset) for every element - # currently in column i which should go to column j for the new - # mapping + # Add an edge to the graph from (i, j+offset) for every element currently in column i which should go to + # column j for the new mapping for row in range(self.num_rows): for column in range(self.num_columns): destination_column = final_positions[row][column].final_column @@ -398,8 +374,7 @@ def __init__( graph[column][destination_column + offset]['num'] = 1 else: graph[column][destination_column + offset]['num'] += 1 - # Find perfect matching, remove those edges from the graph - # and do it again: + # Find perfect matching, remove those edges from the graph and do it again: for i in range(self.num_rows): top_nodes = range(self.num_columns) matching = nx.bipartite.maximum_matching(graph, top_nodes) @@ -423,7 +398,7 @@ def __init__( element = final_positions[row][column] if element.row_after_step_1 is not None: continue - elif element.final_column == dest_column: + if element.final_column == dest_column: if best_element is None: best_element = element elif best_element.final_row > element.final_row: @@ -440,12 +415,11 @@ def __init__( swap_operations += swaps return swap_operations - def _send_possible_commands(self): + def _send_possible_commands(self): # pylint: disable=too-many-branches """ Sends the stored commands possible without changing the mapping. - Note: self._current_row_major_mapping (hence also self.current_mapping) - must exist already + Note: self._current_row_major_mapping (hence also self.current_mapping) must exist already """ active_ids = deepcopy(self._currently_allocated_ids) for logical_id in self._current_row_major_mapping: @@ -508,10 +482,8 @@ def _send_possible_commands(self): elif qb1 - qb0 == 1 and qb1 % self.num_columns != 0: send_gate = True if send_gate: - # Note: This sends the cmd correctly with the backend ids - # as it looks up the mapping in self.current_mapping - # and not our internal mapping - # self._current_row_major_mapping + # Note: This sends the cmd correctly with the backend ids as it looks up the mapping in + # self.current_mapping and not our internal mapping self._current_row_major_mapping self._send_cmd_with_mapped_ids(cmd) else: for qureg in cmd.all_qubits: @@ -520,15 +492,13 @@ def _send_possible_commands(self): new_stored_commands.append(cmd) self._stored_commands = new_stored_commands - def _run(self): + def _run(self): # pylint: disable=too-many-locals.too-many-branches,too-many-statements """ Creates a new mapping and executes possible gates. - It first allocates all 0, ..., self.num_qubits-1 mapped qubit ids, if - they are not already used because we might need them all for the - swaps. Then it creates a new map, swaps all the qubits to the new map, - executes all possible gates, and finally deallocates mapped qubit ids - which don't store any information. + It first allocates all 0, ..., self.num_qubits-1 mapped qubit ids, if they are not already used because we + might need them all for the swaps. Then it creates a new map, swaps all the qubits to the new map, executes + all possible gates, and finally deallocates mapped qubit ids which don't store any information. """ num_of_stored_commands_before = len(self._stored_commands) if not self.current_mapping: @@ -573,9 +543,9 @@ def _run(self): self.send([cmd]) # Send swap operations to arrive at new_mapping: for qubit_id0, qubit_id1 in swaps: - q0 = WeakQubitRef(engine=self, idx=self._mapped_ids_to_backend_ids[qubit_id0]) - q1 = WeakQubitRef(engine=self, idx=self._mapped_ids_to_backend_ids[qubit_id1]) - cmd = Command(engine=self, gate=Swap, qubits=([q0], [q1])) + qb0 = WeakQubitRef(engine=self, idx=self._mapped_ids_to_backend_ids[qubit_id0]) + qb1 = WeakQubitRef(engine=self, idx=self._mapped_ids_to_backend_ids[qubit_id1]) + cmd = Command(engine=self, gate=Swap, qubits=([qb0], [qb1])) self.send([cmd]) # Register statistics: self.num_mappings += 1 @@ -609,24 +579,21 @@ def _run(self): # Check that mapper actually made progress if len(self._stored_commands) == num_of_stored_commands_before: raise RuntimeError( - "Mapper is potentially in an infinite loop. " - + "It is likely that the algorithm requires " - + "too many qubits. Increase the number of " - + "qubits for this mapper." + "Mapper is potentially in an infinite loop. It is likely that the algorithm requires too" + "many qubits. Increase the number of qubits for this mapper." ) def receive(self, command_list): """ - Receives a command list and, for each command, stores it until - we do a mapping (FlushGate or Cache of stored commands is full). + Receives a command list and, for each command, stores it until we do a mapping (FlushGate or Cache of stored + commands is full). Args: - command_list (list of Command objects): list of commands to - receive. + command_list (list of Command objects): list of commands to receive. """ for cmd in command_list: if isinstance(cmd.gate, FlushGate): - while len(self._stored_commands): + while self._stored_commands: self._run() self.send([cmd]) else: diff --git a/projectq/libs/__init__.py b/projectq/libs/__init__.py index 16fc4afdf..0690f2b10 100755 --- a/projectq/libs/__init__.py +++ b/projectq/libs/__init__.py @@ -12,3 +12,5 @@ # 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. + +"""ProjectQ module containing libraries expanding the basic functionalities of ProjectQ""" diff --git a/projectq/libs/hist/_histogram.py b/projectq/libs/hist/_histogram.py index 849bddbd4..85c0be1b3 100644 --- a/projectq/libs/hist/_histogram.py +++ b/projectq/libs/hist/_histogram.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Functions to plot a histogram of measured data""" + from __future__ import print_function import matplotlib.pyplot as plt @@ -39,11 +41,11 @@ def histogram(backend, qureg): Don't forget to call eng.flush() before using this function. """ qubit_list = [] - for q in qureg: - if isinstance(q, list): - qubit_list.extend(q) + for qb in qureg: + if isinstance(qb, list): + qubit_list.extend(qb) else: - qubit_list.append(q) + qubit_list.append(qb) if len(qubit_list) > 5: print('Warning: For {0} qubits there are 2^{0} different outcomes'.format(len(qubit_list))) diff --git a/projectq/libs/math/_constantmath.py b/projectq/libs/math/_constantmath.py index f8f693d85..25eb6def7 100755 --- a/projectq/libs/math/_constantmath.py +++ b/projectq/libs/math/_constantmath.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Module containing constant math quantum operations""" + import math try: @@ -26,7 +28,7 @@ # Draper's addition by constant https://arxiv.org/abs/quant-ph/0008033 -def add_constant(eng, c, quint): +def add_constant(eng, constant, quint): """ Adds a classical constant c to the quantum integer (qureg) quint using Draper addition. @@ -38,24 +40,25 @@ def add_constant(eng, c, quint): with Compute(eng): QFT | quint - for i in range(len(quint)): + for i, qubit in enumerate(quint): for j in range(i, -1, -1): - if (c >> j) & 1: - R(math.pi / (1 << (i - j))) | quint[i] + if (constant >> j) & 1: + R(math.pi / (1 << (i - j))) | qubit Uncompute(eng) # Modular adder by Beauregard https://arxiv.org/abs/quant-ph/0205095 -def add_constant_modN(eng, c, N, quint): +def add_constant_modN(eng, constant, N, quint): # pylint: disable=invalid-name """ Adds a classical constant c to a quantum integer (qureg) quint modulo N using Draper addition and the construction from https://arxiv.org/abs/quant-ph/0205095. """ - assert c < N and c >= 0 + if constant < 0 or constant > N: + raise ValueError('Pre-condition failed: 0 <= constant < N') - AddConstant(c) | quint + AddConstant(constant) | quint with Compute(eng): SubConstant(N) | quint @@ -64,7 +67,7 @@ def add_constant_modN(eng, c, N, quint): with Control(eng, ancilla): AddConstant(N) | quint - SubConstant(c) | quint + SubConstant(constant) | quint with CustomUncompute(eng): X | quint[-1] @@ -72,12 +75,12 @@ def add_constant_modN(eng, c, N, quint): X | quint[-1] del ancilla - AddConstant(c) | quint + AddConstant(constant) | quint # Modular multiplication by modular addition & shift, followed by uncompute # from https://arxiv.org/abs/quant-ph/0205095 -def mul_by_constant_modN(eng, c, N, quint_in): +def mul_by_constant_modN(eng, constant, N, quint_in): # pylint: disable=invalid-name """ Multiplies a quantum integer by a classical number a modulo N, i.e., @@ -86,29 +89,34 @@ def mul_by_constant_modN(eng, c, N, quint_in): (only works if a and N are relative primes, otherwise the modular inverse does not exist). """ - assert c < N and c >= 0 - assert gcd(c, N) == 1 + if constant < 0 or constant > N: + raise ValueError('Pre-condition failed: 0 <= constant < N') + if gcd(constant, N) != 1: + raise ValueError('Pre-condition failed: gcd(constant, N) == 1') - n = len(quint_in) - quint_out = eng.allocate_qureg(n + 1) + n_qubits = len(quint_in) + quint_out = eng.allocate_qureg(n_qubits + 1) - for i in range(n): + for i in range(n_qubits): with Control(eng, quint_in[i]): - AddConstantModN((c << i) % N, N) | quint_out + AddConstantModN((constant << i) % N, N) | quint_out - for i in range(n): + for i in range(n_qubits): Swap | (quint_out[i], quint_in[i]) - cinv = inv_mod_N(c, N) + cinv = inv_mod_N(constant, N) - for i in range(n): + for i in range(n_qubits): with Control(eng, quint_in[i]): SubConstantModN((cinv << i) % N, N) | quint_out del quint_out -# calculates the inverse of a modulo N -def inv_mod_N(a, N): +def inv_mod_N(a, N): # pylint: disable=invalid-name + """ + Calculate the inverse of a modulo N + """ + # pylint: disable=invalid-name s = 0 old_s = 1 r = N diff --git a/projectq/libs/math/_constantmath_test.py b/projectq/libs/math/_constantmath_test.py index 4de8e430d..83eaf060e 100755 --- a/projectq/libs/math/_constantmath_test.py +++ b/projectq/libs/math/_constantmath_test.py @@ -54,6 +54,18 @@ def eng(): rule_set = DecompositionRuleSet(modules=[projectq.libs.math, qft2crandhadamard, swap2cnot]) +@pytest.mark.parametrize( + 'gate', (AddConstantModN(-1, 6), MultiplyByConstantModN(-1, 6), MultiplyByConstantModN(4, 4)), ids=str +) +def test_invalid(eng, gate): + qureg = eng.allocate_qureg(4) + init(eng, qureg, 4) + + with pytest.raises(ValueError): + gate | qureg + eng.flush() + + def test_adder(eng): qureg = eng.allocate_qureg(4) init(eng, qureg, 4) diff --git a/projectq/libs/math/_default_rules.py b/projectq/libs/math/_default_rules.py index 89e3548e5..60e25dd8b 100755 --- a/projectq/libs/math/_default_rules.py +++ b/projectq/libs/math/_default_rules.py @@ -59,31 +59,31 @@ def _replace_addconstant(cmd): eng = cmd.engine - c = cmd.gate.a + const = cmd.gate.a quint = cmd.qubits[0] with Control(eng, cmd.control_qubits): - add_constant(eng, c, quint) + add_constant(eng, const, quint) -def _replace_addconstmodN(cmd): +def _replace_addconstmodN(cmd): # pylint: disable=invalid-name eng = cmd.engine - c = cmd.gate.a + const = cmd.gate.a N = cmd.gate.N quint = cmd.qubits[0] with Control(eng, cmd.control_qubits): - add_constant_modN(eng, c, N, quint) + add_constant_modN(eng, const, N, quint) -def _replace_multiplybyconstantmodN(cmd): +def _replace_multiplybyconstantmodN(cmd): # pylint: disable=invalid-name eng = cmd.engine - c = cmd.gate.a + const = cmd.gate.a N = cmd.gate.N quint = cmd.qubits[0] with Control(eng, cmd.control_qubits): - mul_by_constant_modN(eng, c, N, quint) + mul_by_constant_modN(eng, const, N, quint) def _replace_addquantum(cmd): @@ -92,17 +92,17 @@ def _replace_addquantum(cmd): quint_a = cmd.qubits[0] quint_b = cmd.qubits[1] if len(cmd.qubits) == 3: - c = cmd.qubits[2] - add_quantum(eng, quint_a, quint_b, c) + carry = cmd.qubits[2] + add_quantum(eng, quint_a, quint_b, carry) else: add_quantum(eng, quint_a, quint_b) else: quint_a = cmd.qubits[0] quint_b = cmd.qubits[1] if len(cmd.qubits) == 3: - c = cmd.qubits[2] + carry = cmd.qubits[2] with Control(eng, cmd.control_qubits): - quantum_conditional_add_carry(eng, quint_a, quint_b, cmd.control_qubits, c) + quantum_conditional_add_carry(eng, quint_a, quint_b, cmd.control_qubits, carry) else: with Control(eng, cmd.control_qubits): quantum_conditional_add(eng, quint_a, quint_b, cmd.control_qubits) @@ -126,10 +126,10 @@ def _replace_comparator(cmd): eng = cmd.engine quint_a = cmd.qubits[0] quint_b = cmd.qubits[1] - c = cmd.qubits[2] + carry = cmd.qubits[2] with Control(eng, cmd.control_qubits): - comparator(eng, quint_a, quint_b, c) + comparator(eng, quint_a, quint_b, carry) def _replace_quantumdivision(cmd): diff --git a/projectq/libs/math/_gates.py b/projectq/libs/math/_gates.py index 794281432..45b2bc1fe 100755 --- a/projectq/libs/math/_gates.py +++ b/projectq/libs/math/_gates.py @@ -33,7 +33,7 @@ class AddConstant(BasicMathGate): be a quantum register for the compiler/decomposition to work. """ - def __init__(self, a): + def __init__(self, a): # pylint: disable=invalid-name """ Initializes the gate to the number to add. @@ -44,7 +44,7 @@ def __init__(self, a): corresponding function, so it can be emulated efficiently. """ BasicMathGate.__init__(self, lambda x: ((x + a),)) - self.a = a + self.a = a # pylint: disable=invalid-name def get_inverse(self): """ @@ -65,7 +65,7 @@ def __ne__(self, other): return not self.__eq__(other) -def SubConstant(a): +def SubConstant(a): # pylint: disable=invalid-name """ Subtract a constant from a quantum number represented by a quantum register, stored from low- to high-bit. @@ -118,7 +118,7 @@ def __init__(self, a, N): corresponding function, so it can be emulated efficiently. """ BasicMathGate.__init__(self, lambda x: ((x + a) % N,)) - self.a = a + self.a = a # pylint: disable=invalid-name self.N = N def __str__(self): @@ -141,7 +141,7 @@ def __ne__(self, other): return not self.__eq__(other) -def SubConstantModN(a, N): +def SubConstantModN(a, N): # pylint: disable=invalid-name """ Subtract a constant from a quantum number represented by a quantum register modulo N. @@ -195,7 +195,7 @@ class MultiplyByConstantModN(BasicMathGate): * The value stored in the quantum register must be lower than N """ - def __init__(self, a, N): + def __init__(self, a, N): # pylint: disable=invalid-name """ Initializes the gate to the number to multiply with modulo N. @@ -208,7 +208,7 @@ def __init__(self, a, N): corresponding function, so it can be emulated efficiently. """ BasicMathGate.__init__(self, lambda x: ((a * x) % N,)) - self.a = a + self.a = a # pylint: disable=invalid-name self.N = N def __str__(self): @@ -258,12 +258,12 @@ def __ne__(self, other): return not self.__eq__(other) def get_math_function(self, qubits): - n = len(qubits[0]) + n_qubits = len(qubits[0]) - def math_fun(a): + def math_fun(a): # pylint: disable=invalid-name a[1] = a[0] + a[1] - if len(bin(a[1])[2:]) > n: - a[1] = a[1] % (2 ** n) + if len(bin(a[1])[2:]) > n_qubits: + a[1] = a[1] % (2 ** n_qubits) if len(a) == 3: # Flip the last bit of the carry register @@ -296,7 +296,7 @@ def __str__(self): return "_InverseAddQuantum" def get_math_function(self, qubits): - def math_fun(a): + def math_fun(a): # pylint: disable=invalid-name if len(a) == 3: # Flip the last bit of the carry register a[2] ^= 1 @@ -326,11 +326,11 @@ class SubtractQuantumGate(BasicMathGate): def __init__(self): """ - Initializes the gate to its base class, BasicMathGate, with the - corresponding function, so it can be emulated efficiently. + Initializes the gate to its base class, BasicMathGate, with the corresponding function, so it can be emulated + efficiently. """ - def subtract(a, b): + def subtract(a, b): # pylint: disable=invalid-name return (a, b - a) BasicMathGate.__init__(self, subtract) @@ -349,8 +349,7 @@ def __ne__(self, other): def get_inverse(self): """ - Return the inverse gate (subtraction of the same number a modulo the - same number N). + Return the inverse gate (subtraction of the same number a modulo the same number N). """ return AddQuantum @@ -360,8 +359,7 @@ def get_inverse(self): class ComparatorQuantumGate(BasicMathGate): """ - Flips a compare qubit if the binary value of first imput is higher than - the second input. + Flips a compare qubit if the binary value of first imput is higher than the second input. The numbers are stored from low- to high-bit, i.e., qunum[0] is the LSB. Example: .. code-block:: python @@ -379,11 +377,12 @@ class ComparatorQuantumGate(BasicMathGate): def __init__(self): """ - Initializes the gate and its base class, BasicMathGate, with the - corresponding function, so it can be emulated efficiently. + Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated + efficiently. """ - def compare(a, b, c): + def compare(a, b, c): # pylint: disable=invalid-name + # pylint: disable=invalid-name if b < a: if c == 0: c = 1 @@ -417,11 +416,9 @@ def get_inverse(self): class DivideQuantumGate(BasicMathGate): """ - Divides one quantum number from another. Takes three inputs which should - be quantum registers of equal size; a dividend, a remainder and a - divisor. The remainder should be in the state |0...0> and the dividend - should be bigger than the divisor.The gate returns (in this order): the - remainder, the quotient and the divisor. + Divides one quantum number from another. Takes three inputs which should be quantum registers of equal size; a + dividend, a remainder and a divisor. The remainder should be in the state |0...0> and the dividend should be + bigger than the divisor.The gate returns (in this order): the remainder, the quotient and the divisor. The numbers are stored from low- to high-bit, i.e., qunum[0] is the LSB. @@ -444,17 +441,16 @@ class DivideQuantumGate(BasicMathGate): def __init__(self): """ - Initializes the gate and its base class, BasicMathGate, with the - corresponding function, so it can be emulated efficiently. + Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated + efficiently. """ def division(dividend, remainder, divisor): if divisor == 0 or divisor > dividend: return (remainder, dividend, divisor) - else: - quotient = remainder + dividend // divisor - return ((dividend - (quotient * divisor)), quotient, divisor) + quotient = remainder + dividend // divisor + return ((dividend - (quotient * divisor)), quotient, divisor) BasicMathGate.__init__(self, division) @@ -479,8 +475,7 @@ def __ne__(self, other): class _InverseDivideQuantumGate(BasicMathGate): """ - Internal gate glass to support emulation for inverse - division. + Internal gate glass to support emulation for inverse division. """ def __init__(self): @@ -500,11 +495,10 @@ def __str__(self): class MultiplyQuantumGate(BasicMathGate): """ - Multiplies two quantum numbers represented by a quantum registers. - Requires three quantum registers as inputs, the first two are the - numbers to be multiplied and should have the same size (n qubits). The - third register will hold the product and should be of size 2n+1. - The numbers are stored from low- to high-bit, i.e., qunum[0] is the LSB. + Multiplies two quantum numbers represented by a quantum registers. Requires three quantum registers as inputs, + the first two are the numbers to be multiplied and should have the same size (n qubits). The third register will + hold the product and should be of size 2n+1. The numbers are stored from low- to high-bit, i.e., qunum[0] is the + LSB. Example: .. code-block:: python @@ -520,11 +514,11 @@ class MultiplyQuantumGate(BasicMathGate): def __init__(self): """ - Initializes the gate and its base class, BasicMathGate, with the - corresponding function, so it can be emulated efficiently. + Initializes the gate and its base class, BasicMathGate, with the corresponding function, so it can be emulated + efficiently. """ - def multiply(a, b, c): + def multiply(a, b, c): # pylint: disable=invalid-name return (a, b, c + a * b) BasicMathGate.__init__(self, multiply) @@ -550,12 +544,11 @@ def get_inverse(self): class _InverseMultiplyQuantumGate(BasicMathGate): """ - Internal gate glass to support emulation for inverse - multiplication. + Internal gate glass to support emulation for inverse multiplication. """ def __init__(self): - def inverse_multiplication(a, b, c): + def inverse_multiplication(a, b, c): # pylint: disable=invalid-name return (a, b, c - a * b) BasicMathGate.__init__(self, inverse_multiplication) diff --git a/projectq/libs/math/_quantummath.py b/projectq/libs/math/_quantummath.py index 8e9acfcc2..bb86329fd 100644 --- a/projectq/libs/math/_quantummath.py +++ b/projectq/libs/math/_quantummath.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Definition of some mathematical quantum operations""" + from projectq.ops import All, X, CNOT from projectq.meta import Control from ._gates import AddQuantum, SubtractQuantum @@ -38,43 +40,44 @@ def add_quantum(eng, quint_a, quint_b, carry=None): .. rubric:: References - Quantum addition using ripple carry from: - https://arxiv.org/pdf/0910.2530.pdf + Quantum addition using ripple carry from: https://arxiv.org/pdf/0910.2530.pdf """ # pylint: disable = pointless-statement - assert len(quint_a) == len(quint_b) - assert carry and len(carry) == 1 or not carry + if len(quint_a) != len(quint_b): + raise ValueError('quint_a and quint_b must have the same size!') + if carry and len(carry) != 1: + raise ValueError('Either no carry bit or a single carry qubit is allowed!') - n = len(quint_a) + 1 + n_qubits = len(quint_a) + 1 - for i in range(1, n - 1): + for i in range(1, n_qubits - 1): CNOT | (quint_a[i], quint_b[i]) if carry: - CNOT | (quint_a[n - 2], carry) + CNOT | (quint_a[n_qubits - 2], carry) - for j in range(n - 3, 0, -1): + for j in range(n_qubits - 3, 0, -1): CNOT | (quint_a[j], quint_a[j + 1]) - for k in range(0, n - 2): + for k in range(0, n_qubits - 2): with Control(eng, [quint_a[k], quint_b[k]]): X | (quint_a[k + 1]) if carry: - with Control(eng, [quint_a[n - 2], quint_b[n - 2]]): + with Control(eng, [quint_a[n_qubits - 2], quint_b[n_qubits - 2]]): X | carry - for l in range(n - 2, 0, -1): # noqa: E741 - CNOT | (quint_a[l], quint_b[l]) - with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): - X | quint_a[l] + for i in range(n_qubits - 2, 0, -1): # noqa: E741 + CNOT | (quint_a[i], quint_b[i]) + with Control(eng, [quint_a[i - 1], quint_b[i - 1]]): + X | quint_a[i] - for m in range(1, n - 2): - CNOT | (quint_a[m], quint_a[m + 1]) + for j in range(1, n_qubits - 2): + CNOT | (quint_a[j], quint_a[j + 1]) - for n in range(0, n - 1): - CNOT | (quint_a[n], quint_b[n]) + for n_qubits in range(0, n_qubits - 1): + CNOT | (quint_a[n_qubits], quint_b[n_qubits]) def subtract_quantum(eng, quint_a, quint_b): @@ -91,11 +94,9 @@ def subtract_quantum(eng, quint_a, quint_b): quint_b (list): Quantum register (or list of qubits) Notes: - Quantum subtraction using bitwise complementation of quantum - adder: b-a = (a + b')'. Same as the quantum addition circuit - except that the steps involving the carry qubit are left out - and complement b at the start and at the end of the circuit is - added. + Quantum subtraction using bitwise complementation of quantum adder: b-a = (a + b')'. Same as the quantum + addition circuit except that the steps involving the carry qubit are left out and complement b at the start + and at the end of the circuit is added. Ancilla: 0, size: 9n-8, toffoli: 2n-2, depth: 5n-5. @@ -107,31 +108,32 @@ def subtract_quantum(eng, quint_a, quint_b): """ # pylint: disable = pointless-statement, expression-not-assigned - assert len(quint_a) == len(quint_b) - n = len(quint_a) + 1 + if len(quint_a) != len(quint_b): + raise ValueError('quint_a and quint_b must have the same size!') + n_qubits = len(quint_a) + 1 All(X) | quint_b - for i in range(1, n - 1): + for i in range(1, n_qubits - 1): CNOT | (quint_a[i], quint_b[i]) - for j in range(n - 3, 0, -1): + for j in range(n_qubits - 3, 0, -1): CNOT | (quint_a[j], quint_a[j + 1]) - for k in range(0, n - 2): + for k in range(0, n_qubits - 2): with Control(eng, [quint_a[k], quint_b[k]]): X | (quint_a[k + 1]) - for l in range(n - 2, 0, -1): # noqa: E741 - CNOT | (quint_a[l], quint_b[l]) - with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): - X | quint_a[l] + for i in range(n_qubits - 2, 0, -1): # noqa: E741 + CNOT | (quint_a[i], quint_b[i]) + with Control(eng, [quint_a[i - 1], quint_b[i - 1]]): + X | quint_a[i] - for m in range(1, n - 2): - CNOT | (quint_a[m], quint_a[m + 1]) + for j in range(1, n_qubits - 2): + CNOT | (quint_a[j], quint_a[j + 1]) - for n in range(0, n - 1): - CNOT | (quint_a[n], quint_b[n]) + for n_qubits in range(0, n_qubits - 1): + CNOT | (quint_a[n_qubits], quint_b[n_qubits]) All(X) | quint_b @@ -148,7 +150,8 @@ def inverse_add_quantum_carry(eng, quint_a, quint_b): # pylint: disable = pointless-statement, expression-not-assigned # pylint: disable = unused-argument - assert len(quint_a) == len(quint_b[0]) + if len(quint_a) != len(quint_b[0]): + raise ValueError('quint_a and quint_b must have the same size!') All(X) | quint_b[0] X | quint_b[1][0] @@ -167,8 +170,7 @@ def comparator(eng, quint_a, quint_b, comp): else: |a>|b>|c> -> |a>|b>|c> - (only works if quint_a and quint_b are the same size and the comparator - is 1 qubit) + (only works if quint_a and quint_b are the same size and the comparator is 1 qubit) Args: eng (MainEngine): ProjectQ MainEngine @@ -177,48 +179,47 @@ def comparator(eng, quint_a, quint_b, comp): comp (Qubit): Comparator qubit Notes: - Comparator flipping a compare qubit by computing the high bit - of b-a, which is 1 if and only if a > b. The high bit is - computed using the first half of circuit in AddQuantum (such - that the high bit is written to the carry qubit) and then - undoing the first half of the circuit. By complementing b at - the start and b+a at the end the high bit of b-a is - calculated. + Comparator flipping a compare qubit by computing the high bit of b-a, which is 1 if and only if a > b. The + high bit is computed using the first half of circuit in AddQuantum (such that the high bit is written to the + carry qubit) and then undoing the first half of the circuit. By complementing b at the start and b+a at the + end the high bit of b-a is calculated. Ancilla: 0, size: 8n-3, toffoli: 2n+1, depth: 4n+3. """ # pylint: disable = pointless-statement, expression-not-assigned - assert len(quint_a) == len(quint_b) - assert len(comp) == 1 + if len(quint_a) != len(quint_b): + raise ValueError('quint_a and quint_b must have the same size!') + if len(comp) != 1: + raise ValueError('Comparator output qubit must be a single qubit!') - n = len(quint_a) + 1 + n_qubits = len(quint_a) + 1 All(X) | quint_b - for i in range(1, n - 1): + for i in range(1, n_qubits - 1): CNOT | (quint_a[i], quint_b[i]) - CNOT | (quint_a[n - 2], comp) + CNOT | (quint_a[n_qubits - 2], comp) - for j in range(n - 3, 0, -1): + for j in range(n_qubits - 3, 0, -1): CNOT | (quint_a[j], quint_a[j + 1]) - for k in range(0, n - 2): + for k in range(0, n_qubits - 2): with Control(eng, [quint_a[k], quint_b[k]]): X | (quint_a[k + 1]) - with Control(eng, [quint_a[n - 2], quint_b[n - 2]]): + with Control(eng, [quint_a[n_qubits - 2], quint_b[n_qubits - 2]]): X | comp - for k in range(0, n - 2): + for k in range(0, n_qubits - 2): with Control(eng, [quint_a[k], quint_b[k]]): X | (quint_a[k + 1]) - for j in range(n - 3, 0, -1): + for j in range(n_qubits - 3, 0, -1): CNOT | (quint_a[j], quint_a[j + 1]) - for i in range(1, n - 1): + for i in range(1, n_qubits - 1): CNOT | (quint_a[i], quint_b[i]) All(X) | quint_b @@ -249,35 +250,37 @@ def quantum_conditional_add(eng, quint_a, quint_b, conditional): """ # pylint: disable = pointless-statement, expression-not-assigned - assert len(quint_a) == len(quint_b) - assert len(conditional) == 1 + if len(quint_a) != len(quint_b): + raise ValueError('quint_a and quint_b must have the same size!') + if len(conditional) != 1: + raise ValueError('Conditional qubit must be a single qubit!') - n = len(quint_a) + 1 + n_qubits = len(quint_a) + 1 - for i in range(1, n - 1): + for i in range(1, n_qubits - 1): CNOT | (quint_a[i], quint_b[i]) - for i in range(n - 2, 1, -1): + for i in range(n_qubits - 2, 1, -1): CNOT | (quint_a[i - 1], quint_a[i]) - for k in range(0, n - 2): + for k in range(0, n_qubits - 2): with Control(eng, [quint_a[k], quint_b[k]]): X | (quint_a[k + 1]) - with Control(eng, [quint_a[n - 2], conditional[0]]): - X | quint_b[n - 2] + with Control(eng, [quint_a[n_qubits - 2], conditional[0]]): + X | quint_b[n_qubits - 2] - for l in range(n - 2, 0, -1): # noqa: E741 - with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): - X | quint_a[l] - with Control(eng, [quint_a[l - 1], conditional[0]]): - X | (quint_b[l - 1]) + for i in range(n_qubits - 2, 0, -1): # noqa: E741 + with Control(eng, [quint_a[i - 1], quint_b[i - 1]]): + X | quint_a[i] + with Control(eng, [quint_a[i - 1], conditional[0]]): + X | (quint_b[i - 1]) - for m in range(1, n - 2): - CNOT | (quint_a[m], quint_a[m + 1]) + for j in range(1, n_qubits - 2): + CNOT | (quint_a[j], quint_a[j + 1]) - for o in range(1, n - 1): - CNOT | (quint_a[o], quint_b[o]) + for k in range(1, n_qubits - 1): + CNOT | (quint_a[k], quint_b[k]) def quantum_division(eng, dividend, remainder, divisor): @@ -302,36 +305,35 @@ def quantum_division(eng, dividend, remainder, divisor): Quantum Restoring Integer Division from: https://arxiv.org/pdf/1609.01241.pdf. """ - # pylint: disable = pointless-statement, expression-not-assigned - # The circuit consits of three parts # i) leftshift # ii) subtraction # iii) conditional add operation. - assert len(dividend) == len(remainder) == len(divisor) + if not len(dividend) == len(remainder) == len(divisor): + raise ValueError('Size mismatch in dividend, divisor and remainder!') j = len(remainder) - n = len(dividend) + n_dividend = len(dividend) while j != 0: combined_reg = [] - combined_reg.append(dividend[n - 1]) + combined_reg.append(dividend[n_dividend - 1]) - for i in range(0, n - 1): + for i in range(0, n_dividend - 1): combined_reg.append(remainder[i]) - SubtractQuantum | (divisor[0:n], combined_reg) - CNOT | (combined_reg[n - 1], remainder[n - 1]) - with Control(eng, remainder[n - 1]): - AddQuantum | (divisor[0:n], combined_reg) - X | remainder[n - 1] + SubtractQuantum | (divisor[0:n_dividend], combined_reg) + CNOT | (combined_reg[n_dividend - 1], remainder[n_dividend - 1]) + with Control(eng, remainder[n_dividend - 1]): + AddQuantum | (divisor[0:n_dividend], combined_reg) + X | remainder[n_dividend - 1] - remainder.insert(0, dividend[n - 1]) - dividend.insert(0, remainder[n]) - del remainder[n] - del dividend[n] + remainder.insert(0, dividend[n_dividend - 1]) + dividend.insert(0, remainder[n_dividend]) + del remainder[n_dividend] + del dividend[n_dividend] j -= 1 @@ -348,14 +350,13 @@ def inverse_quantum_division(eng, remainder, quotient, divisor): remainder (list): Quantum register (or list of qubits) divisor (list): Quantum register (or list of qubits) """ - # pylint: disable = pointless-statement, expression-not-assigned - - assert len(remainder) == len(quotient) == len(divisor) + if not len(quotient) == len(remainder) == len(divisor): + raise ValueError('Size mismatch in quotient, divisor and remainder!') j = 0 - n = len(quotient) + n_quotient = len(quotient) - while j != n: + while j != n_quotient: X | quotient[0] with Control(eng, quotient[0]): SubtractQuantum | (divisor, remainder) @@ -363,14 +364,14 @@ def inverse_quantum_division(eng, remainder, quotient, divisor): AddQuantum | (divisor, remainder) - remainder.insert(n, quotient[0]) - quotient.insert(n, remainder[0]) + remainder.insert(n_quotient, quotient[0]) + quotient.insert(n_quotient, remainder[0]) del remainder[0] del quotient[0] j += 1 -def quantum_conditional_add_carry(eng, quint_a, quint_b, ctrl, z): +def quantum_conditional_add_carry(eng, quint_a, quint_b, ctrl, z): # pylint: disable=invalid-name """ Adds up two quantum integers if the control qubit is |1>, i.e., @@ -396,53 +397,53 @@ def quantum_conditional_add_carry(eng, quint_a, quint_b, ctrl, z): .. rubric:: References - Quantum conditional add with no input carry from: - https://arxiv.org/pdf/1706.05113.pdf + Quantum conditional add with no input carry from: https://arxiv.org/pdf/1706.05113.pdf """ - # pylint: disable = pointless-statement, expression-not-assigned + if len(quint_a) != len(quint_b): + raise ValueError('quint_a and quint_b must have the same size!') + if len(ctrl) != 1: + raise ValueError('Only a single control qubit is allowed!') + if len(z) != 2: + raise ValueError('Z quantum register must have 2 qubits!') - assert len(quint_a) == len(quint_b) - assert len(ctrl) == 1 - assert len(z) == 2 + n_a = len(quint_a) - n = len(quint_a) - - for i in range(1, n): + for i in range(1, n_a): CNOT | (quint_a[i], quint_b[i]) - with Control(eng, [quint_a[n - 1], ctrl[0]]): + with Control(eng, [quint_a[n_a - 1], ctrl[0]]): X | z[0] - for j in range(n - 2, 0, -1): + for j in range(n_a - 2, 0, -1): CNOT | (quint_a[j], quint_a[j + 1]) - for k in range(0, n - 1): + for k in range(0, n_a - 1): with Control(eng, [quint_b[k], quint_a[k]]): X | quint_a[k + 1] - with Control(eng, [quint_b[n - 1], quint_a[n - 1]]): + with Control(eng, [quint_b[n_a - 1], quint_a[n_a - 1]]): X | z[1] with Control(eng, [ctrl[0], z[1]]): X | z[0] - with Control(eng, [quint_b[n - 1], quint_a[n - 1]]): + with Control(eng, [quint_b[n_a - 1], quint_a[n_a - 1]]): X | z[1] - for l in range(n - 1, 0, -1): # noqa: E741 - with Control(eng, [ctrl[0], quint_a[l]]): - X | quint_b[l] - with Control(eng, [quint_a[l - 1], quint_b[l - 1]]): - X | quint_a[l] + for i in range(n_a - 1, 0, -1): # noqa: E741 + with Control(eng, [ctrl[0], quint_a[i]]): + X | quint_b[i] + with Control(eng, [quint_a[i - 1], quint_b[i - 1]]): + X | quint_a[i] with Control(eng, [quint_a[0], ctrl[0]]): X | quint_b[0] - for m in range(1, n - 1): - CNOT | (quint_a[m], quint_a[m + 1]) + for j in range(1, n_a - 1): + CNOT | (quint_a[j], quint_a[j + 1]) - for n in range(1, n): - CNOT | (quint_a[n], quint_b[n]) + for n_a in range(1, n_a): + CNOT | (quint_a[n_a], quint_b[n_a]) def quantum_multiplication(eng, quint_a, quint_b, product): @@ -451,8 +452,7 @@ def quantum_multiplication(eng, quint_a, quint_b, product): |a>|b>|0> -> |a>|b>|a*b> - (only works if quint_a and quint_b are of the same size, n qubits and - product has size 2n+1). + (only works if quint_a and quint_b are of the same size, n qubits and product has size 2n+1). Args: eng (MainEngine): ProjectQ MainEngine @@ -462,37 +462,37 @@ def quantum_multiplication(eng, quint_a, quint_b, product): the result Notes: - Ancilla: 2n + 1, size: 7n^2 - 9n + 4, toffoli: 5n^2 - 4n, - depth: 3n^2 - 2. + Ancilla: 2n + 1, size: 7n^2 - 9n + 4, toffoli: 5n^2 - 4n, depth: 3n^2 - 2. .. rubric:: References Quantum multiplication from: https://arxiv.org/abs/1706.05113. """ - # pylint: disable = pointless-statement, expression-not-assigned + n_a = len(quint_a) - assert len(quint_a) == len(quint_b) - n = len(quint_a) - assert len(product) == ((2 * n) + 1) + if len(quint_a) != len(quint_b): + raise ValueError('quint_a and quint_b must have the same size!') + if len(product) != ((2 * n_a) + 1): + raise ValueError('product size must be 2*n + 1') - for i in range(0, n): + for i in range(0, n_a): with Control(eng, [quint_a[i], quint_b[0]]): X | product[i] with Control(eng, quint_b[1]): AddQuantum | ( - quint_a[0 : (n - 1)], # noqa: E203 - product[1:n], - [product[n + 1], product[n + 2]], + quint_a[0 : (n_a - 1)], # noqa: E203 + product[1:n_a], + [product[n_a + 1], product[n_a + 2]], ) - for j in range(2, n): + for j in range(2, n_a): with Control(eng, quint_b[j]): AddQuantum | ( - quint_a[0 : (n - 1)], # noqa: E203 - product[(0 + j) : (n - 1 + j)], # noqa: E203 - [product[n + j], product[n + j + 1]], + quint_a[0 : (n_a - 1)], # noqa: E203 + product[(0 + j) : (n_a - 1 + j)], # noqa: E203 + [product[n_a + j], product[n_a + j + 1]], ) @@ -502,37 +502,36 @@ def inverse_quantum_multiplication(eng, quint_a, quint_b, product): |a>|b>|a*b> -> |a>|b>|0> - (only works if quint_a and quint_b are of the same size, n qubits and - product has size 2n+1) + (only works if quint_a and quint_b are of the same size, n qubits and product has size 2n+1) Args: eng (MainEngine): ProjectQ MainEngine quint_a (list): Quantum register (or list of qubits) quint_b (list): Quantum register (or list of qubits) - product (list): Quantum register (or list of qubits) storing - the result + product (list): Quantum register (or list of qubits) storing the result """ - # pylint: disable = pointless-statement, expression-not-assigned + n_a = len(quint_a) - assert len(quint_a) == len(quint_b) - n = len(quint_a) - assert len(product) == ((2 * n) + 1) + if len(quint_a) != len(quint_b): + raise ValueError('quint_a and quint_b must have the same size!') + if len(product) != ((2 * n_a) + 1): + raise ValueError('product size must be 2*n + 1') - for j in range(2, n): + for j in range(2, n_a): with Control(eng, quint_b[j]): SubtractQuantum | ( - quint_a[0 : (n - 1)], # noqa: E203 - product[(0 + j) : (n - 1 + j)], # noqa: E203 - [product[n + j], product[n + j + 1]], + quint_a[0 : (n_a - 1)], # noqa: E203 + product[(0 + j) : (n_a - 1 + j)], # noqa: E203 + [product[n_a + j], product[n_a + j + 1]], ) - for i in range(0, n): + for i in range(0, n_a): with Control(eng, [quint_a[i], quint_b[0]]): X | product[i] with Control(eng, quint_b[1]): SubtractQuantum | ( - quint_a[0 : (n - 1)], # noqa: E203 - product[1:n], - [product[n + 1], product[n + 2]], + quint_a[0 : (n_a - 1)], # noqa: E203 + product[1:n_a], + [product[n_a + 1], product[n_a + 2]], ) diff --git a/projectq/libs/math/_quantummath_test.py b/projectq/libs/math/_quantummath_test.py index ac936691a..c51fd81b6 100644 --- a/projectq/libs/math/_quantummath_test.py +++ b/projectq/libs/math/_quantummath_test.py @@ -31,7 +31,7 @@ MultiplyQuantum, ) -from projectq.meta import Control, Compute, Uncompute +from projectq.meta import Control, Compute, Uncompute, Dagger def print_all_probabilities(eng, qureg): @@ -74,6 +74,55 @@ def eng(): ) +@pytest.mark.parametrize('carry', (False, True)) +@pytest.mark.parametrize('inverse', (False, True)) +@pytest.mark.parametrize('qubit_idx', (0, 1, 2)) +def test_quantumadder_size_mismatch(eng, qubit_idx, inverse, carry): + qureg_a = eng.allocate_qureg(4 if qubit_idx != 0 else 3) + qureg_b = eng.allocate_qureg(4 if qubit_idx != 1 else 3) + qureg_c = eng.allocate_qureg(1 if qubit_idx != 2 else 2) + + if carry and inverse: + pytest.skip('Inverse addition with carry not supported') + elif not carry and qubit_idx == 2: + pytest.skip('Invalid test parameter combination') + + with pytest.raises(ValueError): + if inverse: + with Dagger(eng): + AddQuantum | (qureg_a, qureg_b, qureg_c if carry else []) + else: + AddQuantum | (qureg_a, qureg_b, qureg_c if carry else []) + + +@pytest.mark.parametrize('qubit_idx', (0, 1, 2)) +def test_quantum_conditional_adder_size_mismatch(eng, qubit_idx): + qureg_a = eng.allocate_qureg(4 if qubit_idx != 0 else 3) + qureg_b = eng.allocate_qureg(4 if qubit_idx != 1 else 3) + control = eng.allocate_qureg(1 if qubit_idx != 2 else 2) + + with pytest.raises(ValueError): + with Control(eng, control): + AddQuantum | (qureg_a, qureg_b) + + +@pytest.mark.parametrize('inverse', (False, True)) +@pytest.mark.parametrize('qubit_idx', (0, 1, 2, 3)) +def test_quantum_conditional_add_carry_size_mismatch(eng, qubit_idx, inverse): + qureg_a = eng.allocate_qureg(4 if qubit_idx != 0 else 3) + qureg_b = eng.allocate_qureg(4 if qubit_idx != 1 else 3) + qureg_c = eng.allocate_qureg(2 if qubit_idx != 2 else 3) + control = eng.allocate_qureg(1 if qubit_idx != 3 else 2) + + with pytest.raises(ValueError): + with Control(eng, control): + if inverse: + with Dagger(eng): + AddQuantum | (qureg_a, qureg_b, qureg_c) + else: + AddQuantum | (qureg_a, qureg_b, qureg_c) + + def test_quantum_adder(eng): qureg_a = eng.allocate_qureg(4) qureg_b = eng.allocate_qureg(4) @@ -162,6 +211,11 @@ def test_quantumsubtraction(eng): assert 1.0 == pytest.approx(eng.backend.get_probability([1, 0, 1, 0, 0], qureg_a)) assert 1.0 == pytest.approx(eng.backend.get_probability([0, 1, 0, 0, 0], qureg_b)) + # Size mismatch + with pytest.raises(ValueError): + SubtractQuantum | (qureg_a, qureg_b[:-1]) + eng.flush() + init(eng, qureg_a, 5) # reset init(eng, qureg_b, 2) # reset @@ -201,11 +255,48 @@ def test_comparator(eng): assert 1.0 == pytest.approx(eng.backend.get_probability([1], compare_qubit)) + # Size mismatch in qubit registers + with pytest.raises(ValueError): + ComparatorQuantum | (qureg_a, qureg_b[:-1], compare_qubit) + + # Only single qubit for compare qubit + with pytest.raises(ValueError): + ComparatorQuantum | (qureg_a, qureg_b, [*compare_qubit, *compare_qubit]) + All(Measure) | qureg_a All(Measure) | qureg_b Measure | compare_qubit +@pytest.mark.parametrize('qubit_idx', (0, 1, 2)) +def test_quantumdivision_size_mismatch(eng, qubit_idx): + qureg_a = eng.allocate_qureg(4 if qubit_idx != 0 else 3) + qureg_b = eng.allocate_qureg(4 if qubit_idx != 1 else 3) + qureg_c = eng.allocate_qureg(4 if qubit_idx != 2 else 3) + + with pytest.raises(ValueError): + DivideQuantum | (qureg_a, qureg_b, qureg_c) + + All(Measure) | qureg_a + All(Measure) | qureg_b + All(Measure) | qureg_c + + +@pytest.mark.parametrize('qubit_idx', (0, 1, 2)) +def test_quantumdivision_size_mismatch_inverse(eng, qubit_idx): + qureg_a = eng.allocate_qureg(4 if qubit_idx != 0 else 3) + qureg_b = eng.allocate_qureg(4 if qubit_idx != 1 else 3) + qureg_c = eng.allocate_qureg(4 if qubit_idx != 2 else 3) + + with pytest.raises(ValueError): + with Dagger(eng): + DivideQuantum | (qureg_a, qureg_b, qureg_c) + + All(Measure) | qureg_a + All(Measure) | qureg_b + All(Measure) | qureg_c + + def test_quantumdivision(eng): qureg_a = eng.allocate_qureg(4) qureg_b = eng.allocate_qureg(4) @@ -242,6 +333,35 @@ def test_quantumdivision(eng): All(Measure) | qureg_c +@pytest.mark.parametrize('qubit_idx', (0, 1, 2)) +def test_quantummultiplication_size_mismatch(eng, qubit_idx): + qureg_a = eng.allocate_qureg(3 if qubit_idx != 0 else 2) + qureg_b = eng.allocate_qureg(3 if qubit_idx != 1 else 2) + qureg_c = eng.allocate_qureg(7 if qubit_idx != 2 else 6) + + with pytest.raises(ValueError): + MultiplyQuantum | (qureg_a, qureg_b, qureg_c) + + All(Measure) | qureg_a + All(Measure) | qureg_b + All(Measure) | qureg_c + + +@pytest.mark.parametrize('qubit_idx', (0, 1, 2)) +def test_quantummultiplication_size_mismatch_inverse(eng, qubit_idx): + qureg_a = eng.allocate_qureg(4 if qubit_idx != 0 else 3) + qureg_b = eng.allocate_qureg(4 if qubit_idx != 1 else 3) + qureg_c = eng.allocate_qureg(4 if qubit_idx != 2 else 3) + + with pytest.raises(ValueError): + with Dagger(eng): + MultiplyQuantum | (qureg_a, qureg_b, qureg_c) + + All(Measure) | qureg_a + All(Measure) | qureg_b + All(Measure) | qureg_c + + def test_quantummultiplication(eng): qureg_a = eng.allocate_qureg(3) qureg_b = eng.allocate_qureg(3) diff --git a/projectq/libs/revkit/__init__.py b/projectq/libs/revkit/__init__.py index 8f422f811..8cbf8bc17 100644 --- a/projectq/libs/revkit/__init__.py +++ b/projectq/libs/revkit/__init__.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Module containing code to interface with RevKit""" + from ._permutation import PermutationOracle from ._control_function import ControlFunctionOracle from ._phase import PhaseOracle diff --git a/projectq/libs/revkit/_control_function.py b/projectq/libs/revkit/_control_function.py index a64e749eb..d80613cf8 100644 --- a/projectq/libs/revkit/_control_function.py +++ b/projectq/libs/revkit/_control_function.py @@ -13,12 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""RevKit support for control function oracles""" + + from projectq.ops import BasicGate from ._utils import _exec -class ControlFunctionOracle: +class ControlFunctionOracle: # pylint: disable=too-few-public-methods """ Synthesizes a negation controlled by an arbitrary control function. @@ -57,15 +60,15 @@ def __init__(self, function, **kwargs): self.function = function else: try: - import dormouse + import dormouse # pylint: disable=import-outside-toplevel self.function = dormouse.to_truth_table(function) - except ImportError: # pragma: no cover + except ImportError as err: # pragma: no cover raise RuntimeError( "The dormouse library needs to be installed in order to " "automatically compile Python code into functions. Try " "to install dormouse with 'pip install dormouse'." - ) + ) from err self.kwargs = kwargs self._check_function() @@ -81,12 +84,13 @@ def __or__(self, qubits): target qubit. """ try: - import revkit - except ImportError: # pragma: no cover + import revkit # pylint: disable=import-outside-toplevel + except ImportError as err: # pragma: no cover raise RuntimeError( "The RevKit Python library needs to be installed and in the " "PYTHONPATH in order to call this function" - ) + ) from err + # pylint: disable=invalid-name # convert qubits to tuple qs = [] diff --git a/projectq/libs/revkit/_permutation.py b/projectq/libs/revkit/_permutation.py index cc4891f95..71384f828 100644 --- a/projectq/libs/revkit/_permutation.py +++ b/projectq/libs/revkit/_permutation.py @@ -13,12 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""RevKit support for permutation oracles""" + from projectq.ops import BasicGate from ._utils import _exec -class PermutationOracle: +class PermutationOracle: # pylint: disable=too-few-public-methods """ Synthesizes a permutation using RevKit. @@ -60,13 +62,14 @@ def __or__(self, qubits): applied. """ try: - import revkit - except ImportError: # pragma: no cover + import revkit # pylint: disable=import-outside-toplevel + except ImportError as err: # pragma: no cover raise RuntimeError( "The RevKit Python library needs to be installed and in the " "PYTHONPATH in order to call this function" - ) + ) from err + # pylint: disable=invalid-name # convert qubits to flattened list qs = BasicGate.make_tuple_of_qureg(qubits) qs = sum(qs, []) diff --git a/projectq/libs/revkit/_phase.py b/projectq/libs/revkit/_phase.py index f30d72191..d50539d92 100644 --- a/projectq/libs/revkit/_phase.py +++ b/projectq/libs/revkit/_phase.py @@ -13,12 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""RevKit support for phase oracles""" + from projectq.ops import BasicGate from ._utils import _exec -class PhaseOracle: +class PhaseOracle: # pylint: disable=too-few-public-methods """ Synthesizes phase circuit from an arbitrary Boolean function. @@ -61,15 +63,15 @@ def __init__(self, function, **kwargs): self.function = function else: try: - import dormouse + import dormouse # pylint: disable=import-outside-toplevel self.function = dormouse.to_truth_table(function) - except ImportError: # pragma: no cover + except ImportError as err: # pragma: no cover raise RuntimeError( "The dormouse library needs to be installed in order to " "automatically compile Python code into functions. Try " "to install dormouse with 'pip install dormouse'." - ) + ) from err self.kwargs = kwargs self._check_function() @@ -83,13 +85,14 @@ def __or__(self, qubits): applied. """ try: - import revkit - except ImportError: # pragma: no cover + import revkit # pylint: disable=import-outside-toplevel + except ImportError as err: # pragma: no cover raise RuntimeError( "The RevKit Python library needs to be installed and in the " "PYTHONPATH in order to call this function" - ) + ) from err + # pylint: disable=invalid-name # convert qubits to tuple qs = [] for item in BasicGate.make_tuple_of_qureg(qubits): @@ -105,7 +108,7 @@ def __or__(self, qubits): revkit.tt(table="{0:#0{1}x}".format(self.function, hex_length)) # create phase circuit from truth table - self.kwargs.get("synth", lambda: revkit.esopps())() + self.kwargs.get("synth", revkit.esopps)() # check whether circuit has correct signature if revkit.ps(mct=True, silent=True)['qubits'] != len(qs): diff --git a/projectq/libs/revkit/_utils.py b/projectq/libs/revkit/_utils.py index 5871d7d6d..573451bf6 100644 --- a/projectq/libs/revkit/_utils.py +++ b/projectq/libs/revkit/_utils.py @@ -14,13 +14,20 @@ # limitations under the License. +"""Module containing some utility functions""" + +# flake8: noqa +# pylint: skip-file + + def _exec(code, qs): """ Executes the Python code in 'filename'. Args: code (string): ProjectQ code. - qs (tuple): Qubits to which the permutation is being - applied. + qubits (tuple): Qubits to which the permutation is being applied. """ + from projectq.ops import C, X, Z, All + exec(code) diff --git a/projectq/meta/__init__.py b/projectq/meta/__init__.py index ce321f0f0..27c20b835 100755 --- a/projectq/meta/__init__.py +++ b/projectq/meta/__init__.py @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -The projectq.meta package features meta instructions which help both the user -and the compiler in writing/producing efficient code. It includes, e.g., +The projectq.meta package features meta instructions which help both the user and the compiler in writing/producing +efficient code. It includes, e.g., * Loop (with Loop(eng): ...) * Compute/Uncompute (with Compute(eng): ..., [...], Uncompute(eng)) diff --git a/projectq/meta/_compute.py b/projectq/meta/_compute.py index d2adbf16a..f09eb8514 100755 --- a/projectq/meta/_compute.py +++ b/projectq/meta/_compute.py @@ -15,22 +15,21 @@ """ Compute, Uncompute, CustomUncompute. -Contains Compute, Uncompute, and CustomUncompute classes which can be used to -annotate Compute / Action / Uncompute sections, facilitating the conditioning -of the entire operation on the value of a qubit / register (only Action needs +Contains Compute, Uncompute, and CustomUncompute classes which can be used to annotate Compute / Action / Uncompute +sections, facilitating the conditioning of the entire operation on the value of a qubit / register (only Action needs controls). This file also defines the corresponding meta tags. """ from copy import deepcopy -import projectq -from projectq.cengines import BasicEngine +from projectq.cengines import BasicEngine, CommandModifier from projectq.ops import Allocate, Deallocate + from ._util import insert_engine, drop_engine_after class QubitManagementError(Exception): - pass + """Exception raised when the lifetime of a qubit is problematic within a loop""" class NoComputeSectionError(Exception): @@ -38,10 +37,8 @@ class NoComputeSectionError(Exception): Exception raised if uncompute is called but no compute section found. """ - pass - -class ComputeTag(object): +class ComputeTag: """ Compute meta tag. """ @@ -53,7 +50,7 @@ def __ne__(self, other): return not self.__eq__(other) -class UncomputeTag(object): +class UncomputeTag: """ Uncompute meta tag. """ @@ -65,6 +62,17 @@ def __ne__(self, other): return not self.__eq__(other) +def _add_uncompute_tag(cmd): + """ + Modify the command tags, inserting an UncomputeTag. + + Args: + cmd (Command): Command to modify. + """ + cmd.tags.append(UncomputeTag()) + return cmd + + class ComputeEngine(BasicEngine): """ Adds Compute-tags to all commands and stores them (to later uncompute them @@ -82,31 +90,19 @@ def __init__(self): self._allocated_qubit_ids = set() self._deallocated_qubit_ids = set() - def _add_uncompute_tag(self, cmd): - """ - Modify the command tags, inserting an UncomputeTag. - - Args: - cmd (Command): Command to modify. - """ - cmd.tags.append(UncomputeTag()) - return cmd - - def run_uncompute(self): + def run_uncompute(self): # pylint: disable=too-many-branches,too-many-statements """ Send uncomputing gates. - Sends the inverse of the stored commands in reverse order down to the - next engine. And also deals with allocated qubits in Compute section. - If a qubit has been allocated during compute, it will be deallocated - during uncompute. If a qubit has been allocated and deallocated during - compute, then a new qubit is allocated and deallocated during - uncompute. + Sends the inverse of the stored commands in reverse order down to the next engine. And also deals with + allocated qubits in Compute section. If a qubit has been allocated during compute, it will be deallocated + during uncompute. If a qubit has been allocated and deallocated during compute, then a new qubit is allocated + and deallocated during uncompute. """ # No qubits allocated during Compute section -> do standard uncompute if len(self._allocated_qubit_ids) == 0: - self.send([self._add_uncompute_tag(cmd.get_inverse()) for cmd in reversed(self._l)]) + self.send([_add_uncompute_tag(cmd.get_inverse()) for cmd in reversed(self._l)]) return # qubits ids which were allocated and deallocated in Compute section @@ -131,16 +127,20 @@ def run_uncompute(self): break if not qubit_found: raise QubitManagementError("\nQubit was not found in " + "MainEngine.active_qubits.\n") - self.send([self._add_uncompute_tag(cmd.get_inverse())]) + self.send([_add_uncompute_tag(cmd.get_inverse())]) else: - self.send([self._add_uncompute_tag(cmd.get_inverse())]) + self.send([_add_uncompute_tag(cmd.get_inverse())]) return # There was at least one qubit allocated and deallocated within # compute section. Handle uncompute in most general case new_local_id = dict() for cmd in reversed(self._l): if cmd.gate == Deallocate: - assert (cmd.qubits[0][0].id) in ids_local_to_compute + if not cmd.qubits[0][0].id in ids_local_to_compute: # pragma: no cover + raise RuntimeError( + 'Internal compiler error: qubit being deallocated is not found in the list of qubits local to ' + 'the Compute section' + ) # Create new local qubit which lives within uncompute section @@ -149,7 +149,7 @@ def add_uncompute(command, old_tags=deepcopy(cmd.tags)): command.tags = old_tags + [UncomputeTag()] return command - tagger_eng = projectq.cengines.CommandModifier(add_uncompute) + tagger_eng = CommandModifier(add_uncompute) insert_engine(self, tagger_eng) new_local_qb = self.allocate_qubit() drop_engine_after(self) @@ -166,7 +166,7 @@ def add_uncompute(command, old_tags=deepcopy(cmd.tags)): old_id = deepcopy(cmd.qubits[0][0].id) cmd.qubits[0][0].id = new_local_id[cmd.qubits[0][0].id] del new_local_id[old_id] - self.send([self._add_uncompute_tag(cmd.get_inverse())]) + self.send([_add_uncompute_tag(cmd.get_inverse())]) else: # Deallocate qubit which was allocated in compute section: @@ -183,7 +183,7 @@ def add_uncompute(command, old_tags=deepcopy(cmd.tags)): break if not qubit_found: raise QubitManagementError("\nQubit was not found in " + "MainEngine.active_qubits.\n") - self.send([self._add_uncompute_tag(cmd.get_inverse())]) + self.send([_add_uncompute_tag(cmd.get_inverse())]) else: # Process commands by replacing each local qubit from @@ -195,19 +195,18 @@ def add_uncompute(command, old_tags=deepcopy(cmd.tags)): if qubit.id in new_local_id: qubit.id = new_local_id[qubit.id] - self.send([self._add_uncompute_tag(cmd.get_inverse())]) + self.send([_add_uncompute_tag(cmd.get_inverse())]) def end_compute(self): """ End the compute step (exit the with Compute() - statement). - Will tell the Compute-engine to stop caching. It then waits for the - uncompute instruction, which is when it sends all cached commands - inverted and in reverse order down to the next compiler engine. + Will tell the Compute-engine to stop caching. It then waits for the uncompute instruction, which is when it + sends all cached commands inverted and in reverse order down to the next compiler engine. Raises: - QubitManagementError: If qubit has been deallocated in Compute - section which has not been allocated in Compute section + QubitManagementError: If qubit has been deallocated in Compute section which has not been allocated in + Compute section """ self._compute = False if not self._allocated_qubit_ids.issuperset(self._deallocated_qubit_ids): @@ -218,9 +217,8 @@ def end_compute(self): def receive(self, command_list): """ - If in compute-mode, receive commands and store deepcopy of each cmd. - Add ComputeTag to received cmd and send it on. Otherwise, send all - received commands directly to next_engine. + If in compute-mode, receive commands and store deepcopy of each cmd. Add ComputeTag to received cmd and send + it on. Otherwise, send all received commands directly to next_engine. Args: command_list (list): List of commands to receive. @@ -270,7 +268,7 @@ def receive(self, command_list): self.send([cmd]) -class Compute(object): +class Compute: """ Start a compute-section. @@ -283,9 +281,8 @@ class Compute(object): Uncompute(eng) # runs inverse of the compute section Warning: - If qubits are allocated within the compute section, they must either be - uncomputed and deallocated within that section or, alternatively, - uncomputed and deallocated in the following uncompute section. + If qubits are allocated within the compute section, they must either be uncomputed and deallocated within that + section or, alternatively, uncomputed and deallocated in the following uncompute section. This means that the following examples are valid: @@ -314,12 +311,10 @@ class Compute(object): Uncompute(eng) # will deallocate the ancilla! - After the uncompute section, ancilla qubits allocated within the - compute section will be invalid (and deallocated). The same holds when - using CustomUncompute. + After the uncompute section, ancilla qubits allocated within the compute section will be invalid (and + deallocated). The same holds when using CustomUncompute. - Failure to comply with these rules results in an exception being - thrown. + Failure to comply with these rules results in an exception being thrown. """ def __init__(self, engine): @@ -327,8 +322,7 @@ def __init__(self, engine): Initialize a Compute context. Args: - engine (BasicEngine): Engine which is the first to receive all - commands (normally: MainEngine). + engine (BasicEngine): Engine which is the first to receive all commands (normally: MainEngine). """ self.engine = engine self._compute_eng = None @@ -337,13 +331,13 @@ def __enter__(self): self._compute_eng = ComputeEngine() insert_engine(self.engine, self._compute_eng) - def __exit__(self, type, value, traceback): + def __exit__(self, exc_type, exc_value, exc_traceback): # notify ComputeEngine that the compute section is done self._compute_eng.end_compute() self._compute_eng = None -class CustomUncompute(object): +class CustomUncompute: """ Start a custom uncompute-section. @@ -357,8 +351,8 @@ class CustomUncompute(object): do_something_inverse(qubits) Raises: - QubitManagementError: If qubits are allocated within Compute or within - CustomUncompute context but are not deallocated. + QubitManagementError: If qubits are allocated within Compute or within CustomUncompute context but are not + deallocated. """ def __init__(self, engine): @@ -366,13 +360,13 @@ def __init__(self, engine): Initialize a CustomUncompute context. Args: - engine (BasicEngine): Engine which is the first to receive all - commands (normally: MainEngine). + engine (BasicEngine): Engine which is the first to receive all commands (normally: MainEngine). """ self.engine = engine # Save all qubit ids from qubits which are created or destroyed. self._allocated_qubit_ids = set() self._deallocated_qubit_ids = set() + self._uncompute_eng = None def __enter__(self): # first, remove the compute engine @@ -391,11 +385,11 @@ def __enter__(self): self._uncompute_eng = UncomputeEngine() insert_engine(self.engine, self._uncompute_eng) - def __exit__(self, type, value, traceback): + def __exit__(self, exc_type, exc_value, exc_traceback): # If an error happens in this context, qubits might not have been # deallocated because that code section was not yet executed, # so don't check and raise an additional error. - if type is not None: + if exc_type is not None: return # Check that all qubits allocated within Compute or within # CustomUncompute have been deallocated. @@ -411,7 +405,7 @@ def __exit__(self, type, value, traceback): drop_engine_after(self.engine) -def Uncompute(engine): +def Uncompute(engine): # pylint: disable=invalid-name """ Uncompute automatically. diff --git a/projectq/meta/_control.py b/projectq/meta/_control.py index 704c76374..97a25af90 100755 --- a/projectq/meta/_control.py +++ b/projectq/meta/_control.py @@ -24,11 +24,11 @@ """ from projectq.cengines import BasicEngine -from projectq.meta import ComputeTag, UncomputeTag -from projectq.ops import ClassicalInstructionGate +from projectq.ops import ClassicalInstructionGate, CtrlAll from projectq.types import BasicQubit + +from ._compute import ComputeTag, UncomputeTag from ._util import insert_engine, drop_engine_after -from projectq.ops import CtrlAll def canonical_ctrl_state(ctrl_state, num_qubits): @@ -45,6 +45,7 @@ def canonical_ctrl_state(ctrl_state, num_qubits): Note: In case of integer values for `ctrl_state`, the least significant bit applies to the first qubit in the qubit register, e.g. if ctrl_state == 2, its binary representation if '10' with the least significan bit being 0. + This means in particular that the followings are equivalent: .. code-block:: python @@ -85,6 +86,19 @@ def canonical_ctrl_state(ctrl_state, num_qubits): raise TypeError('Input must be a string, an integer or an enum value of class State') +def _has_compute_uncompute_tag(cmd): + """ + Return True if command cmd has a compute/uncompute tag. + + Args: + cmd (Command object): a command object. + """ + for tag in cmd.tags: + if tag in [UncomputeTag(), ComputeTag()]: + return True + return False + + class ControlEngine(BasicEngine): """ Adds control qubits to all commands that have no compute / uncompute tags. @@ -102,29 +116,18 @@ def __init__(self, qubits, ctrl_state=CtrlAll.One): self._qubits = qubits self._state = ctrl_state - def _has_compute_uncompute_tag(self, cmd): - """ - Return True if command cmd has a compute/uncompute tag. - - Args: - cmd (Command object): a command object. - """ - for t in cmd.tags: - if t in [UncomputeTag(), ComputeTag()]: - return True - return False - def _handle_command(self, cmd): - if not self._has_compute_uncompute_tag(cmd) and not isinstance(cmd.gate, ClassicalInstructionGate): + if not _has_compute_uncompute_tag(cmd) and not isinstance(cmd.gate, ClassicalInstructionGate): cmd.add_control_qubits(self._qubits, self._state) self.send([cmd]) def receive(self, command_list): + """Forward all commands to the next engine.""" for cmd in command_list: self._handle_command(cmd) -class Control(object): +class Control: """ Condition an entire code block on the value of qubits being 1. @@ -151,7 +154,8 @@ def __init__(self, engine, qubits, ctrl_state=CtrlAll.One): ... """ self.engine = engine - assert not isinstance(qubits, tuple) + if isinstance(qubits, tuple): + raise TypeError('Control qubits must be a list, not a tuple!') if isinstance(qubits, BasicQubit): qubits = [qubits] self._qubits = qubits @@ -159,10 +163,10 @@ def __init__(self, engine, qubits, ctrl_state=CtrlAll.One): def __enter__(self): if len(self._qubits) > 0: - ce = ControlEngine(self._qubits, self._state) - insert_engine(self.engine, ce) + engine = ControlEngine(self._qubits, self._state) + insert_engine(self.engine, engine) - def __exit__(self, type, value, traceback): + def __exit__(self, exc_type, exc_value, exc_traceback): # remove control handler from engine list (i.e. skip it) if len(self._qubits) > 0: drop_engine_after(self.engine) diff --git a/projectq/meta/_control_test.py b/projectq/meta/_control_test.py index cc79c4591..eadadad34 100755 --- a/projectq/meta/_control_test.py +++ b/projectq/meta/_control_test.py @@ -82,10 +82,9 @@ def test_control_engine_has_compute_tag(): test_cmd0.tags = [DirtyQubitTag(), ComputeTag(), DirtyQubitTag()] test_cmd1.tags = [DirtyQubitTag(), UncomputeTag(), DirtyQubitTag()] test_cmd2.tags = [DirtyQubitTag()] - control_eng = _control.ControlEngine("MockEng", ctrl_state=CtrlAll.One) - assert control_eng._has_compute_uncompute_tag(test_cmd0) - assert control_eng._has_compute_uncompute_tag(test_cmd1) - assert not control_eng._has_compute_uncompute_tag(test_cmd2) + assert _control._has_compute_uncompute_tag(test_cmd0) + assert _control._has_compute_uncompute_tag(test_cmd1) + assert not _control._has_compute_uncompute_tag(test_cmd2) def test_control(): @@ -114,6 +113,9 @@ def test_control(): assert backend.received_commands[4].control_qubits[1].id == qureg[1].id assert backend.received_commands[6].control_qubits[0].id == qureg[0].id + with pytest.raises(TypeError): + _control.Control(eng, (qureg[0], qureg[1])) + def test_control_state(): backend = DummyEngine(save_commands=True) diff --git a/projectq/meta/_dagger.py b/projectq/meta/_dagger.py index bbe2a3d54..86d28857c 100755 --- a/projectq/meta/_dagger.py +++ b/projectq/meta/_dagger.py @@ -28,7 +28,7 @@ class QubitManagementError(Exception): - pass + """Exception raised when the lifetime of a qubit is problematic within a loop""" class DaggerEngine(BasicEngine): @@ -78,7 +78,7 @@ def receive(self, command_list): self._commands.extend(command_list) -class Dagger(object): +class Dagger: """ Invert an entire code block. @@ -132,11 +132,11 @@ def __enter__(self): self._dagger_eng = DaggerEngine() insert_engine(self.engine, self._dagger_eng) - def __exit__(self, type, value, traceback): + def __exit__(self, exc_type, exc_value, exc_traceback): # If an error happens in this context, qubits might not have been # deallocated because that code section was not yet executed, # so don't check and raise an additional error. - if type is not None: + if exc_type is not None: return # run dagger engine self._dagger_eng.run() diff --git a/projectq/meta/_dirtyqubit.py b/projectq/meta/_dirtyqubit.py index 3a3ec455a..a26cc574a 100755 --- a/projectq/meta/_dirtyqubit.py +++ b/projectq/meta/_dirtyqubit.py @@ -17,7 +17,7 @@ """ -class DirtyQubitTag(object): +class DirtyQubitTag: """ Dirty qubit meta tag """ diff --git a/projectq/meta/_logicalqubit.py b/projectq/meta/_logicalqubit.py index 63ee60514..218456bae 100644 --- a/projectq/meta/_logicalqubit.py +++ b/projectq/meta/_logicalqubit.py @@ -17,7 +17,7 @@ """ -class LogicalQubitIDTag(object): +class LogicalQubitIDTag: """ LogicalQubitIDTag for a mapped qubit to annotate a MeasureGate. diff --git a/projectq/meta/_loop.py b/projectq/meta/_loop.py index d3e61ab5a..f563bb2f0 100755 --- a/projectq/meta/_loop.py +++ b/projectq/meta/_loop.py @@ -31,10 +31,10 @@ class QubitManagementError(Exception): - pass + """Exception raised when the lifetime of a qubit is problematic within a loop""" -class LoopTag(object): +class LoopTag: """ Loop meta tag """ @@ -99,7 +99,7 @@ def run(self): " ...\n" " del qubit[0]\n" ) - if not self._next_engines_support_loop_tag: + if not self._next_engines_support_loop_tag: # pylint: disable=too-many-nested-blocks # Unroll the loop # Check that local qubits have been deallocated: if self._deallocated_qubit_ids != self._allocated_qubit_ids: @@ -130,7 +130,7 @@ def run(self): if self._deallocated_qubit_ids != self._allocated_qubit_ids: raise QubitManagementError(error_message) - def receive(self, command_list): + def receive(self, command_list): # pylint: disable=too-many-branches """ Receive (and potentially temporarily store) all commands. @@ -147,6 +147,7 @@ def receive(self, command_list): unroll or, if there is a LoopTag-handling engine, add the LoopTag. """ + # pylint: disable=too-many-nested-blocks if self._next_engines_support_loop_tag or self.next_engine.is_meta_tag_supported(LoopTag): # Loop tag is supported, send everything with a LoopTag # Don't check is_meta_tag_supported anymore @@ -185,7 +186,7 @@ def receive(self, command_list): self._refs_to_local_qb[qubit.id].append(qubit) -class Loop(object): +class Loop: """ Loop n times over an entire code block. @@ -196,8 +197,8 @@ class Loop(object): # [quantum gates to be executed 4 times] Warning: - If the code in the loop contains allocation of qubits, those qubits - have to be deleted prior to exiting the 'with Loop()' context. + If the code in the loop contains allocation of qubits, those qubits have to be deleted prior to exiting the + 'with Loop()' context. This code is **NOT VALID**: @@ -248,7 +249,7 @@ def __enter__(self): self._loop_eng = LoopEngine(self.num) insert_engine(self.engine, self._loop_eng) - def __exit__(self, type, value, traceback): + def __exit__(self, exc_type, exc_value, exc_traceback): if self.num != 1: # remove loop handler from engine list (i.e. skip it) self._loop_eng.run() diff --git a/projectq/meta/_util.py b/projectq/meta/_util.py index ffbc3dc20..4dab11ede 100755 --- a/projectq/meta/_util.py +++ b/projectq/meta/_util.py @@ -13,6 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Tools to add/remove compiler engines to the MainEngine list +""" + def insert_engine(prev_engine, engine_to_insert): """ diff --git a/projectq/ops/__init__.py b/projectq/ops/__init__.py index 21e4e4031..81b3313ac 100755 --- a/projectq/ops/__init__.py +++ b/projectq/ops/__init__.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ProjectQ module containing all basic gates (operations)""" + from ._basics import ( NotMergeable, NotInvertible, diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py index 6d224b98b..6c9943c78 100755 --- a/projectq/ops/_basics.py +++ b/projectq/ops/_basics.py @@ -31,15 +31,15 @@ needs to be made explicitely, while for one argument it is optional. """ -import math from copy import deepcopy +import math +import unicodedata import numpy as np from projectq.types import BasicQubit from ._command import Command, apply_command -import unicodedata ANGLE_PRECISION = 12 ANGLE_TOLERANCE = 10 ** -ANGLE_PRECISION @@ -49,23 +49,18 @@ class NotMergeable(Exception): """ - Exception thrown when trying to merge two gates which are not mergeable (or - where it is not implemented (yet)). + Exception thrown when trying to merge two gates which are not mergeable (or where it is not implemented (yet)). """ - pass - class NotInvertible(Exception): """ - Exception thrown when trying to invert a gate which is not invertable (or - where the inverse is not implemented (yet)). + Exception thrown when trying to invert a gate which is not invertable (or where the inverse is not implemented + (yet)). """ - pass - -class BasicGate(object): +class BasicGate: """ Base class of all gates. (Don't use it directly but derive from it) """ @@ -84,8 +79,7 @@ def __init__(self): ExampleGate | (a,b,c,d,e) - where a and b are interchangeable. Then, call this function as - follows: + where a and b are interchangeable. Then, call this function as follows: .. code-block:: python @@ -97,8 +91,8 @@ def __init__(self): ExampleGate2 | (a,b,c,d,e) - where a and b are interchangeable and, in addition, c, d, and e - are interchangeable among themselves. Then, call this function as + where a and b are interchangeable and, in addition, c, d, and e are interchangeable among + themselves. Then, call this function as .. code-block:: python @@ -106,7 +100,7 @@ def __init__(self): """ self.interchangeable_qubit_indices = [] - def get_inverse(self): + def get_inverse(self): # pylint: disable=no-self-use """ Return the inverse gate. @@ -117,7 +111,7 @@ def get_inverse(self): """ raise NotInvertible("BasicGate: No get_inverse() implemented.") - def get_merged(self, other): + def get_merged(self, other): # pylint: disable=no-self-use """ Return this gate merged with another gate. @@ -133,9 +127,8 @@ def make_tuple_of_qureg(qubits): """ Convert quantum input of "gate | quantum input" to internal formatting. - A Command object only accepts tuples of Quregs (list of Qubit objects) - as qubits input parameter. However, with this function we allow the - user to use a more flexible syntax: + A Command object only accepts tuples of Quregs (list of Qubit objects) as qubits input parameter. However, + with this function we allow the user to use a more flexible syntax: 1) Gate | qubit 2) Gate | [qubit0, qubit1] @@ -143,9 +136,8 @@ def make_tuple_of_qureg(qubits): 4) Gate | (qubit, ) 5) Gate | (qureg, qubit) - where qubit is a Qubit object and qureg is a Qureg object. This - function takes the right hand side of | and transforms it to the - correct input parameter of a Command object which is: + where qubit is a Qubit object and qureg is a Qureg object. This function takes the right hand side of | and + transforms it to the correct input parameter of a Command object which is: 1) -> Gate | ([qubit], ) 2) -> Gate | ([qubit0, qubit1], ) @@ -154,20 +146,19 @@ def make_tuple_of_qureg(qubits): 5) -> Gate | (qureg, [qubit]) Args: - qubits: a Qubit object, a list of Qubit objects, a Qureg object, - or a tuple of Qubit or Qureg objects (can be mixed). + qubits: a Qubit object, a list of Qubit objects, a Qureg object, or a tuple of Qubit or Qureg objects (can + be mixed). Returns: - Canonical representation (tuple): A tuple containing Qureg - (or list of Qubits) objects. + Canonical representation (tuple): A tuple containing Qureg (or list of Qubits) objects. """ if not isinstance(qubits, tuple): qubits = (qubits,) qubits = list(qubits) - for i in range(len(qubits)): + for i, qubit in enumerate(qubits): if isinstance(qubits[i], BasicQubit): - qubits[i] = [qubits[i]] + qubits[i] = [qubit] return tuple(qubits) @@ -186,7 +177,8 @@ def generate_command(self, qubits): engines = [q.engine for reg in qubits for q in reg] eng = engines[0] - assert all(e is eng for e in engines) + if not all(e is eng for e in engines): + raise ValueError('All qubits must belong to the same engine!') return Command(eng, self, qubits) def __or__(self, qubits): @@ -201,8 +193,8 @@ def __or__(self, qubits): 5) Gate | (qureg, qubit) Args: - qubits: a Qubit object, a list of Qubit objects, a Qureg object, - or a tuple of Qubit or Qureg objects (can be mixed). + qubits: a Qubit object, a list of Qubit objects, a Qureg object, or a tuple of Qubit or Qureg objects (can + be mixed). """ cmd = self.generate_command(qubits) apply_command(cmd) @@ -211,17 +203,14 @@ def __eq__(self, other): """ Equality comparision - Return True if instance of the same class, unless other is an instance - of :class:MatrixGate, in which case equality is to be checked by - testing for existence and (approximate) equality of matrix - representations. + Return True if instance of the same class, unless other is an instance of :class:MatrixGate, in which case + equality is to be checked by testing for existence and (approximate) equality of matrix representations. """ if isinstance(other, self.__class__): return True - elif isinstance(other, MatrixGate): + if isinstance(other, MatrixGate): return NotImplemented - else: - return False + return False def __ne__(self, other): return not self.__eq__(other) @@ -229,19 +218,21 @@ 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): # pylint: disable=unused-argument """ String representation - Achieve same function as str() but can be extended for configurable - 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): + def is_identity(self): # pylint: disable=no-self-use + """ + Return True if the gate is an identity gate. In this base class, always returns False. + """ return False @@ -250,9 +241,8 @@ class MatrixGate(BasicGate): Defines a gate class whose instances are defined by a matrix. Note: - Use this gate class only for gates acting on a small numbers of qubits. - In general, consider instead using one of the provided ProjectQ gates - or define a new class as this allows the compiler to work symbolically. + Use this gate class only for gates acting on a small numbers of qubits. In general, consider instead using + one of the provided ProjectQ gates or define a new class as this allows the compiler to work symbolically. Example: @@ -274,18 +264,24 @@ def __init__(self, matrix=None): @property def matrix(self): + """ + Access to the matrix property of this gate. + """ return self._matrix @matrix.setter def matrix(self, matrix): + """ + Set the matrix property of this gate. + """ self._matrix = np.matrix(matrix) def __eq__(self, other): """ Equality comparision - Return True only if both gates have a matrix respresentation and the - matrices are (approximately) equal. Otherwise return False. + Return True only if both gates have a matrix respresentation and the matrices are (approximately) + equal. Otherwise return False. """ if not hasattr(other, 'matrix'): return False @@ -307,12 +303,11 @@ def get_inverse(self): return MatrixGate(np.linalg.inv(self.matrix)) -class SelfInverseGate(BasicGate): +class SelfInverseGate(BasicGate): # pylint: disable=abstract-method """ Self-inverse basic gate class. - Automatic implementation of the get_inverse-member function for self- - inverse gates. + Automatic implementation of the get_inverse-member function for self- inverse gates. Example: .. code-block:: python @@ -329,11 +324,9 @@ class BasicRotationGate(BasicGate): """ Defines a base class of a rotation gate. - 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). + 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). """ def __init__(self, angle): @@ -366,9 +359,8 @@ def to_string(self, symbols=False): Return the string representation of a BasicRotationGate. Args: - symbols (bool): uses the pi character and round the angle for a - more user friendly display if True, full angle - written in radian otherwise. + symbols (bool): uses the pi character and round the angle for a more user friendly display if True, full + angle written in radian otherwise. """ if symbols: angle = "(" + str(round(self.angle / math.pi, 3)) + unicodedata.lookup("GREEK SMALL LETTER PI") + ")" @@ -395,22 +387,19 @@ 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 + 4 * math.pi) def get_merged(self, other): """ Return self merged with another gate. - Default implementation handles rotation gate of the same type, where - angles are simply added. + Default implementation handles rotation gate of the same type, where angles are simply added. Args: other: Rotation gate of same type. Raises: - NotMergeable: For non-rotation gates or rotation gates of - different type. + NotMergeable: For non-rotation gates or rotation gates of different type. Returns: New object representing the merged gates. @@ -423,8 +412,7 @@ def __eq__(self, other): """Return True if same class and same rotation angle.""" if isinstance(other, self.__class__): return self.angle == other.angle - else: - return False + return False def __ne__(self, other): return not self.__eq__(other) @@ -443,11 +431,9 @@ class BasicPhaseGate(BasicGate): """ Defines a base class of a phase gate. - A phase gate has a continuous parameter (the angle), labeled 'angle' / - self.angle. Its inverse is the same gate with the negated argument. - Phase 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). + A phase gate has a continuous parameter (the angle), labeled 'angle' / self.angle. Its inverse is the same gate + with the negated argument. Phase 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). """ def __init__(self, angle): @@ -489,20 +475,17 @@ def tex_str(self): def get_inverse(self): """ - Return the inverse of this rotation gate (negate the angle, return new - object). + Return the inverse of this rotation gate (negate the angle, return new object). """ if self.angle == 0: return self.__class__(0) - else: - return self.__class__(-self.angle + 2 * math.pi) + return self.__class__(-self.angle + 2 * math.pi) def get_merged(self, other): """ Return self merged with another gate. - Default implementation handles rotation gate of the same type, where - angles are simply added. + Default implementation handles rotation gate of the same type, where angles are simply added. Args: other: Rotation gate of same type. @@ -522,8 +505,7 @@ def __eq__(self, other): """Return True if same class and same rotation angle.""" if isinstance(other, self.__class__): return self.angle == other.angle - else: - return False + return False def __ne__(self, other): return not self.__eq__(other) @@ -533,32 +515,26 @@ def __hash__(self): # Classical instruction gates never have control qubits. -class ClassicalInstructionGate(BasicGate): +class ClassicalInstructionGate(BasicGate): # pylint: disable=abstract-method """ Classical instruction gate. - Base class for all gates which are not quantum gates in the typical sense, - e.g., measurement, allocation/deallocation, ... + Base class for all gates which are not quantum gates in the typical sense, e.g., measurement, + allocation/deallocation, ... """ - pass - -class FastForwardingGate(ClassicalInstructionGate): +class FastForwardingGate(ClassicalInstructionGate): # pylint: disable=abstract-method """ - Base class for classical instruction gates which require a fast-forward - through compiler engines that cache / buffer gates. Examples include - Measure and Deallocate, which both should be executed asap, such - that Measurement results are available and resources are freed, - respectively. + Base class for classical instruction gates which require a fast-forward through compiler engines that cache / + buffer gates. Examples include Measure and Deallocate, which both should be executed asap, such that Measurement + results are available and resources are freed, respectively. Note: - The only requirement is that FlushGate commands run the entire - circuit. FastForwardingGate objects can be used but the user cannot - expect a measurement result to be available for all back-ends when - calling only Measure. E.g., for the IBM Quantum Experience back-end, - sending the circuit for each Measure-gate would be too inefficient, - which is why a final + The only requirement is that FlushGate commands run the entire circuit. FastForwardingGate objects can be used + but the user cannot expect a measurement result to be available for all back-ends when calling only + Measure. E.g., for the IBM Quantum Experience back-end, sending the circuit for each Measure-gate would be too + inefficient, which is why a final .. code-block: python @@ -567,15 +543,13 @@ class FastForwardingGate(ClassicalInstructionGate): is required before the circuit gets sent through the API. """ - pass - class BasicMathGate(BasicGate): """ Base class for all math gates. - It allows efficient emulation by providing a mathematical representation - which is given by the concrete gate which derives from this base class. + It allows efficient emulation by providing a mathematical representation which is given by the concrete gate which + derives from this base class. The AddConstant gate, for example, registers a function of the form .. code-block:: python @@ -583,11 +557,10 @@ class BasicMathGate(BasicGate): def add(x): return (x+a,) - upon initialization. More generally, the function takes integers as - parameters and returns a tuple / list of outputs, each entry corresponding - to the function input. As an example, consider out-of-place - multiplication, which takes two input registers and adds the result into a - third, i.e., (a,b,c) -> (a,b,c+a*b). The corresponding function then is + upon initialization. More generally, the function takes integers as parameters and returns a tuple / list of + outputs, each entry corresponding to the function input. As an example, consider out-of-place multiplication, + which takes two input registers and adds the result into a third, i.e., (a,b,c) -> (a,b,c+a*b). The corresponding + function then is .. code-block:: python @@ -597,14 +570,11 @@ def multiply(a,b,c) def __init__(self, math_fun): """ - Initialize a BasicMathGate by providing the mathematical function that - it implements. + Initialize a BasicMathGate by providing the mathematical function that it implements. Args: - math_fun (function): Function which takes as many int values as - input, as the gate takes registers. For each of these values, - it then returns the output (i.e., it returns a list/tuple of - output values). + math_fun (function): Function which takes as many int values as input, as the gate takes registers. For + each of these values, it then returns the output (i.e., it returns a list/tuple of output values). Example: .. code-block:: python @@ -613,9 +583,8 @@ def add(a,b): return (a,a+b) BasicMathGate.__init__(self, add) - If the gate acts on, e.g., fixed point numbers, the number of bits per - register is also required in order to describe the action of such a - mathematical gate. For this reason, there is + If the gate acts on, e.g., fixed point numbers, the number of bits per register is also required in order to + describe the action of such a mathematical gate. For this reason, there is .. code-block:: python @@ -636,25 +605,24 @@ def math_fun(a): """ BasicGate.__init__(self) - def math_function(x): - return list(math_fun(*x)) + def math_function(arg): + return list(math_fun(*arg)) self._math_function = math_function def __str__(self): return "MATH" - def get_math_function(self, qubits): + def get_math_function(self, qubits): # pylint: disable=unused-argument """ - Return the math function which corresponds to the action of this math - gate, given the input to the gate (a tuple of quantum registers). + Return the math function which corresponds to the action of this math gate, given the input to the gate (a + tuple of quantum registers). Args: - qubits (tuple): Qubits to which the math gate is being - applied. + qubits (tuple): Qubits to which the math gate is being applied. Returns: - math_fun (function): Python function describing the action of this - gate. (See BasicMathGate.__init__ for an example). + math_fun (function): Python function describing the action of this gate. (See BasicMathGate.__init__ for + an example). """ return self._math_function diff --git a/projectq/ops/_basics_test.py b/projectq/ops/_basics_test.py index a47a28441..7fa40643b 100755 --- a/projectq/ops/_basics_test.py +++ b/projectq/ops/_basics_test.py @@ -23,6 +23,7 @@ from projectq.ops import Command, X from projectq import MainEngine from projectq.cengines import DummyEngine +from projectq.types import WeakQubitRef from projectq.ops import _basics @@ -78,6 +79,15 @@ def test_basic_gate_generate_command(main_engine): assert command5 == Command(main_engine, basic_gate, (qureg, [qubit0])) +def test_basic_gate_generate_command_invalid(): + qb0 = WeakQubitRef(1, idx=0) + qb1 = WeakQubitRef(2, idx=0) + + basic_gate = _basics.BasicGate() + with pytest.raises(ValueError): + basic_gate.generate_command([qb0, qb1]) + + def test_basic_gate_or(): saving_backend = DummyEngine(save_commands=True) main_engine = MainEngine(backend=saving_backend, engine_list=[DummyEngine()]) diff --git a/projectq/ops/_command.py b/projectq/ops/_command.py index 8cd4061d4..621bee320 100755 --- a/projectq/ops/_command.py +++ b/projectq/ops/_command.py @@ -21,14 +21,12 @@ CNOT | (qubit1, qubit2) -a Command object is generated which represents both the gate, qubits and -control qubits. This Command object then gets sent down the compilation -pipeline. +a Command object is generated which represents both the gate, qubits and control qubits. This Command object then gets +sent down the compilation pipeline. -In detail, the Gate object overloads the operator| (magic method __or__) -to generate a Command object which stores the qubits in a canonical order -using interchangeable qubit indices defined by the gate to allow the -optimizer to cancel the following two gates +In detail, the Gate object overloads the operator| (magic method __or__) to generate a Command object which stores the +qubits in a canonical order using interchangeable qubit indices defined by the gate to allow the optimizer to cancel +the following two gates .. code-block:: python @@ -40,11 +38,11 @@ """ from copy import deepcopy +from enum import IntEnum import itertools import projectq from projectq.types import WeakQubitRef, Qureg -from enum import IntEnum class IncompatibleControlState(Exception): @@ -52,10 +50,10 @@ class IncompatibleControlState(Exception): Exception thrown when trying to set two incompatible states for a control qubit. """ - pass - class CtrlAll(IntEnum): + """Enum type to initialise the control state of qubits""" + Zero = 0 One = 1 @@ -64,8 +62,7 @@ def apply_command(cmd): """ Apply a command. - Extracts the qubits-owning (target) engine from the Command object - and sends the Command to it. + Extracts the qubits-owning (target) engine from the Command object and sends the Command to it. Args: cmd (Command): Command to apply @@ -74,55 +71,43 @@ def apply_command(cmd): engine.receive([cmd]) -class Command(object): +class Command: # pylint: disable=too-many-instance-attributes """ - Class used as a container to store commands. If a gate is applied to - qubits, then the gate and qubits are saved in a command object. Qubits - are copied into WeakQubitRefs in order to allow early deallocation (would - be kept alive otherwise). WeakQubitRef qubits don't send deallocate gate - when destructed. + Class used as a container to store commands. If a gate is applied to qubits, then the gate and qubits are saved in + a command object. Qubits are copied into WeakQubitRefs in order to allow early deallocation (would be kept alive + otherwise). WeakQubitRef qubits don't send deallocate gate when destructed. Attributes: gate: The gate to execute - qubits: Tuple of qubit lists (e.g. Quregs). Interchangeable qubits - are stored in a unique order + qubits: Tuple of qubit lists (e.g. Quregs). Interchangeable qubits are stored in a unique order control_qubits: The Qureg of control qubits in a unique order engine: The engine (usually: MainEngine) - tags: The list of tag objects associated with this command - (e.g., ComputeTag, UncomputeTag, LoopTag, ...). tag objects need to - support ==, != (__eq__ and __ne__) for comparison as used in e.g. - TagRemover. New tags should always be added to the end of the list. - This means that if there are e.g. two LoopTags in a command, tag[0] - is from the inner scope while tag[1] is from the other scope as the - other scope receives the command after the inner scope LoopEngine - and hence adds its LoopTag to the end. + tags: The list of tag objects associated with this command (e.g., ComputeTag, UncomputeTag, LoopTag, ...). tag + objects need to support ==, != (__eq__ and __ne__) for comparison as used in e.g. TagRemover. New tags + should always be added to the end of the list. This means that if there are e.g. two LoopTags in a command, + tag[0] is from the inner scope while tag[1] is from the other scope as the other scope receives the command + after the inner scope LoopEngine and hence adds its LoopTag to the end. all_qubits: A tuple of control_qubits + qubits """ - def __init__(self, engine, gate, qubits, controls=(), tags=(), control_state=CtrlAll.One): + def __init__( + self, engine, gate, qubits, controls=(), tags=(), control_state=CtrlAll.One + ): # pylint: disable=too-many-arguments """ Initialize a Command object. Note: - control qubits (Command.control_qubits) are stored as a - list of qubits, and command tags (Command.tags) as a list of tag- - objects. All functions within this class also work if - WeakQubitRefs are supplied instead of normal Qubit objects - (see WeakQubitRef). + control qubits (Command.control_qubits) are stored as a list of qubits, and command tags (Command.tags) as + a list of tag- objects. All functions within this class also work if WeakQubitRefs are supplied instead of + normal Qubit objects (see WeakQubitRef). Args: - engine (projectq.cengines.BasicEngine): - engine which created the qubit (mostly the MainEngine) - gate (projectq.ops.Gate): - Gate to be executed - qubits (tuple[Qureg]): - Tuple of quantum registers (to which the gate is applied) - controls (Qureg|list[Qubit]): - Qubits that condition the command. - tags (list[object]): - Tags associated with the command. - control_state(int,str,projectq.meta.CtrlAll) - Control state for any control qubits + engine (projectq.cengines.BasicEngine): engine which created the qubit (mostly the MainEngine) + gate (projectq.ops.Gate): Gate to be executed + qubits (tuple[Qureg]): Tuple of quantum registers (to which the gate is applied) + controls (Qureg|list[Qubit]): Qubits that condition the command. + tags (list[object]): Tags associated with the command. + control_state(int,str,projectq.meta.CtrlAll) Control state for any control qubits """ qubits = tuple([WeakQubitRef(qubit.engine, qubit.id) for qubit in qreg] for qreg in qubits) @@ -136,10 +121,12 @@ def __init__(self, engine, gate, qubits, controls=(), tags=(), control_state=Ctr @property def qubits(self): + """Qubits stored in a Command object""" return self._qubits @qubits.setter def qubits(self, qubits): + """Set the qubits stored in a Command object""" self._qubits = self._order_qubits(qubits) def __deepcopy__(self, memo): @@ -156,12 +143,10 @@ def get_inverse(self): """ Get the command object corresponding to the inverse of this command. - Inverts the gate (if possible) and creates a new command object from - the result. + Inverts the gate (if possible) and creates a new command object from the result. Raises: - NotInvertible: If the gate does not provide an inverse (see - BasicGate.get_inverse) + NotInvertible: If the gate does not provide an inverse (see BasicGate.get_inverse) """ return Command( self._engine, @@ -218,8 +203,8 @@ def _order_qubits(self, qubits): for old_positions in interchangeable_qubit_indices: new_positions = sorted(old_positions, key=lambda x: ordered_qubits[x][0].id) qubits_new_order = [ordered_qubits[i] for i in new_positions] - for i in range(len(old_positions)): - ordered_qubits[old_positions[i]] = qubits_new_order[i] + for i, pos in enumerate(old_positions): + ordered_qubits[pos] = qubits_new_order[i] return tuple(ordered_qubits) @property @@ -255,6 +240,7 @@ def control_qubits(self, qubits): @property def control_state(self): + """Returns the state of the control qubits (ie. either positively- or negatively-controlled)""" return self._control_state @control_state.setter @@ -266,7 +252,7 @@ def control_state(self, state): state (int,str,projectq.meta.CtrtAll): state of control qubit (ie. positive or negative) """ # NB: avoid circular imports - from projectq.meta import canonical_ctrl_state + from projectq.meta import canonical_ctrl_state # pylint: disable=import-outside-toplevel self._control_state = canonical_ctrl_state(state, len(self._control_qubits)) @@ -284,9 +270,10 @@ def add_control_qubits(self, qubits, state=CtrlAll.One): control qubits. """ # NB: avoid circular imports - from projectq.meta import canonical_ctrl_state + from projectq.meta import canonical_ctrl_state # pylint: disable=import-outside-toplevel - assert isinstance(qubits, list) + if not isinstance(qubits, list): + raise ValueError('Control qubits must be a list of qubits!') self._control_qubits.extend([WeakQubitRef(qubit.engine, qubit.id) for qubit in qubits]) self._control_state += canonical_ctrl_state(state, len(qubits)) @@ -297,7 +284,6 @@ def add_control_qubits(self, qubits, state=CtrlAll.One): # Make sure that we do not have contradicting control states for any control qubits for _, data in itertools.groupby(zipped, key=lambda x: x[0].id): qubits, states = list(zip(*data)) - assert len(set(qubits)) == 1 # This should be by design... if len(set(states)) != 1: raise IncompatibleControlState( 'Control qubits {} cannot have conflicting control states: {}'.format(list(qubits), states) diff --git a/projectq/ops/_command_test.py b/projectq/ops/_command_test.py index eb4cb6681..1228f135a 100755 --- a/projectq/ops/_command_test.py +++ b/projectq/ops/_command_test.py @@ -196,6 +196,9 @@ def test_commmand_add_control_qubits_one(main_engine, state): assert cmd.control_qubits[0].id == 1 assert cmd.control_state == canonical_ctrl_state(state, 1) + with pytest.raises(ValueError): + cmd.add_control_qubits(qubit0[0]) + @pytest.mark.parametrize( 'state', diff --git a/projectq/ops/_gates.py b/projectq/ops/_gates.py index 44300f854..a041c18f9 100755 --- a/projectq/ops/_gates.py +++ b/projectq/ops/_gates.py @@ -43,11 +43,9 @@ import math import cmath -import warnings import numpy as np -from projectq.ops import get_inverse from ._basics import ( BasicGate, SelfInverseGate, @@ -57,6 +55,7 @@ FastForwardingGate, ) from ._command import apply_command +from ._metagates import get_inverse class HGate(SelfInverseGate): @@ -67,6 +66,7 @@ def __str__(self): @property def matrix(self): + """Access to the matrix property of this gate""" return 1.0 / cmath.sqrt(2.0) * np.matrix([[1, 1], [1, -1]]) @@ -82,6 +82,7 @@ def __str__(self): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix([[0, 1], [1, 0]]) @@ -97,6 +98,7 @@ def __str__(self): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix([[0, -1j], [1j, 0]]) @@ -112,6 +114,7 @@ def __str__(self): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix([[1, 0], [0, -1]]) @@ -124,6 +127,7 @@ class SGate(BasicGate): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix([[1, 0], [0, 1j]]) def __str__(self): @@ -141,6 +145,7 @@ class TGate(BasicGate): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix([[1, 0], [0, cmath.exp(1j * cmath.pi / 4)]]) def __str__(self): @@ -158,9 +163,13 @@ class SqrtXGate(BasicGate): @property def matrix(self): + """Access to the matrix property of this gate""" return 0.5 * np.matrix([[1 + 1j, 1 - 1j], [1 - 1j, 1 + 1j]]) - def tex_str(self): + def tex_str(self): # pylint: disable=no-self-use + """ + Return the Latex string representation of a SqrtXGate. + """ return r'$\sqrt{X}$' def __str__(self): @@ -183,6 +192,7 @@ def __str__(self): @property def matrix(self): + """Access to the matrix property of this gate""" # fmt: off return np.matrix([[1, 0, 0, 0], [0, 0, 1, 0], @@ -207,6 +217,7 @@ def __str__(self): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix( [ [1, 0, 0, 0], @@ -240,6 +251,7 @@ class Ph(BasicPhaseGate): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix([[cmath.exp(1j * self.angle), 0], [0, cmath.exp(1j * self.angle)]]) @@ -248,6 +260,7 @@ class Rx(BasicRotationGate): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix( [ [math.cos(0.5 * self.angle), -1j * math.sin(0.5 * self.angle)], @@ -261,6 +274,7 @@ class Ry(BasicRotationGate): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix( [ [math.cos(0.5 * self.angle), -math.sin(0.5 * self.angle)], @@ -274,6 +288,7 @@ class Rz(BasicRotationGate): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix( [ [cmath.exp(-0.5 * 1j * self.angle), 0], @@ -287,6 +302,7 @@ class Rxx(BasicRotationGate): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix( [ [cmath.cos(0.5 * self.angle), 0, 0, -1j * cmath.sin(0.5 * self.angle)], @@ -302,6 +318,7 @@ class Ryy(BasicRotationGate): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix( [ [cmath.cos(0.5 * self.angle), 0, 0, 1j * cmath.sin(0.5 * self.angle)], @@ -317,6 +334,7 @@ class Rzz(BasicRotationGate): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix( [ [cmath.exp(-0.5 * 1j * self.angle), 0, 0, 0], @@ -332,6 +350,7 @@ class R(BasicPhaseGate): @property def matrix(self): + """Access to the matrix property of this gate""" return np.matrix([[1, 0], [0, cmath.exp(1j * self.angle)]]) @@ -340,9 +359,8 @@ class FlushGate(FastForwardingGate): Flush gate (denotes the end of the circuit). Note: - All compiler engines (cengines) which cache/buffer gates are obligated - to flush and send all gates to the next compiler engine (followed by - the flush command). + All compiler engines (cengines) which cache/buffer gates are obligated to flush and send all gates to the next + compiler engine (followed by the flush command). Note: This gate is sent when calling @@ -377,13 +395,8 @@ def __or__(self, qubits): num_qubits += 1 cmd = self.generate_command(([qubit],)) apply_command(cmd) - if num_qubits > 1: - warnings.warn( - "Pending syntax change in future versions of " - "ProjectQ: \n Measure will be a single qubit gate " - "only. Use `All(Measure) | qureg` instead to " - "measure multiple qubits." - ) + if num_qubits > 1: # pragma: no cover + raise RuntimeError('Measure is a single qubit gate. Use All(Measure) | qureg instead') #: Shortcut (instance of) :class:`projectq.ops.MeasureGate` diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py index 6f38648ba..59fafb7d3 100644 --- a/projectq/ops/_metagates.py +++ b/projectq/ops/_metagates.py @@ -23,8 +23,7 @@ Tensor(H) | (qubit1, qubit2) # apply H to qubit #1 and #2 As well as the meta functions -* get_inverse (Tries to access the get_inverse member function of a gate - and upon failure returns a DaggeredGate) +* get_inverse (Tries to access the get_inverse member function of a gate and upon failure returns a DaggeredGate) * C (Creates an n-ary controlled version of an arbitrary gate) """ @@ -36,20 +35,16 @@ class ControlQubitError(Exception): Exception thrown when wrong number of control qubits are supplied. """ - pass - class DaggeredGate(BasicGate): """ - Wrapper class allowing to execute the inverse of a gate, even when it does - not define one. + Wrapper class allowing to execute the inverse of a gate, even when it does not define one. - If there is a replacement available, then there is also one for the - inverse, namely the replacement function run in reverse, while inverting - all gates. This class enables using this emulation automatically. + If there is a replacement available, then there is also one for the inverse, namely the replacement function run + in reverse, while inverting all gates. This class enables using this emulation automatically. - A DaggeredGate is returned automatically when employing the get_inverse- - function on a gate which does not provide a get_inverse() member function. + A DaggeredGate is returned automatically when employing the get_inverse- function on a gate which does not provide + a get_inverse() member function. Example: .. code-block:: python @@ -57,10 +52,9 @@ class DaggeredGate(BasicGate): with Dagger(eng): MySpecialGate | qubits - will create a DaggeredGate if MySpecialGate does not implement - get_inverse. If there is a decomposition function available, an auto- - replacer engine can automatically replace the inverted gate by a call to - the decomposition function inside a "with Dagger"-statement. + will create a DaggeredGate if MySpecialGate does not implement get_inverse. If there is a decomposition function + available, an auto- replacer engine can automatically replace the inverted gate by a call to the decomposition + function inside a "with Dagger"-statement. """ def __init__(self, gate): @@ -91,13 +85,11 @@ def tex_str(self): """ if hasattr(self._gate, 'tex_str'): return self._gate.tex_str() + r"${}^\dagger$" - else: - return str(self._gate) + r"${}^\dagger$" + return str(self._gate) + r"${}^\dagger$" def get_inverse(self): """ - Return the inverse gate (the inverse of the inverse of a gate is the - gate itself). + Return the inverse gate (the inverse of the inverse of a gate is the gate itself). """ return self._gate @@ -116,8 +108,7 @@ def get_inverse(gate): """ Return the inverse of a gate. - Tries to call gate.get_inverse and, upon failure, creates a DaggeredGate - instead. + Tries to call gate.get_inverse and, upon failure, creates a DaggeredGate instead. Args: gate: Gate of which to get the inverse @@ -158,10 +149,8 @@ class ControlledGate(BasicGate): Note: Use the meta function :func:`C()` to create a controlled gate - A wrapper class which enables (multi-) controlled gates. It overloads - the __or__-operator, using the first qubits provided as control qubits. - The n control-qubits need to be the first n qubits. They can be in - separate quregs. + A wrapper class which enables (multi-) controlled gates. It overloads the __or__-operator, using the first qubits + provided as control qubits. The n control-qubits need to be the first n qubits. They can be in separate quregs. Example: .. code-block:: python @@ -207,12 +196,10 @@ def get_inverse(self): def __or__(self, qubits): """ - Apply the controlled gate to qubits, using the first n qubits as - controls. + Apply the controlled gate to qubits, using the first n qubits as controls. - Note: The control qubits can be split across the first quregs. - However, the n-th control qubit needs to be the last qubit in a - qureg. The following quregs belong to the gate. + Note: The control qubits can be split across the first quregs. However, the n-th control qubit needs to be + the last qubit in a qureg. The following quregs belong to the gate. Args: qubits (tuple of lists of Qubit objects): qubits to which to apply @@ -238,7 +225,7 @@ def __or__(self, qubits): "the required number of control quregs." ) - import projectq.meta + import projectq.meta # pylint: disable=import-outside-toplevel with projectq.meta.Control(gate_quregs[0][0].engine, ctrl): self._gate | tuple(gate_quregs) @@ -251,27 +238,26 @@ def __ne__(self, other): return not self.__eq__(other) -def C(gate, n=1): +def C(gate, n_qubits=1): """ Return n-controlled version of the provided gate. Args: gate: Gate to turn into its controlled version - n: Number of controls (default: 1) + n_qubits: Number of controls (default: 1) Example: .. code-block:: python C(NOT) | (c, q) # equivalent to CNOT | (c, q) """ - return ControlledGate(gate, n) + return ControlledGate(gate, n_qubits) class Tensor(BasicGate): """ - Wrapper class allowing to apply a (single-qubit) gate to every qubit in a - quantum register. Allowed syntax is to supply either a qureg or a tuple - which contains only one qureg. + Wrapper class allowing to apply a (single-qubit) gate to every qubit in a quantum register. Allowed syntax is to + supply either a qureg or a tuple which contains only one qureg. Example: .. code-block:: python @@ -291,8 +277,7 @@ def __str__(self): def get_inverse(self): """ - Return the inverse of this tensored gate (which is the tensored - inverse of the gate). + Return the inverse of this tensored gate (which is the tensored inverse of the gate). """ return Tensor(get_inverse(self._gate)) @@ -305,9 +290,12 @@ def __ne__(self, other): def __or__(self, qubits): """Applies the gate to every qubit in the quantum register qubits.""" if isinstance(qubits, tuple): - assert len(qubits) == 1 + if len(qubits) != 1: + raise ValueError('Tensor/All must be applied to a single quantum register!') qubits = qubits[0] - assert isinstance(qubits, list) + if not isinstance(qubits, list): + raise ValueError('Tensor/All must be applied to a list of qubits!') + for qubit in qubits: self._gate | qubit diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py index 52b9e00d1..3c99780fc 100755 --- a/projectq/ops/_metagates_test.py +++ b/projectq/ops/_metagates_test.py @@ -34,10 +34,22 @@ ClassicalInstructionGate, All, ) +from projectq.types import WeakQubitRef from projectq.ops import _metagates +def test_tensored_gate_invalid(): + qb0 = WeakQubitRef(None, idx=0) + qb1 = WeakQubitRef(None, idx=1) + + with pytest.raises(ValueError): + _metagates.Tensor(Y) | (qb0, qb1) + + with pytest.raises(ValueError): + _metagates.Tensor(Y) | qb0 + + def test_tensored_controlled_gate(): saving_backend = DummyEngine(save_commands=True) main_engine = MainEngine(backend=saving_backend, engine_list=[DummyEngine()]) diff --git a/projectq/ops/_qaagate.py b/projectq/ops/_qaagate.py index a7a6c438a..9dea09bef 100755 --- a/projectq/ops/_qaagate.py +++ b/projectq/ops/_qaagate.py @@ -13,22 +13,21 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Contains the definition of the quantum amplitude amplification gate""" + from ._basics import BasicGate class QAA(BasicGate): """ - Quantum Aplitude Aplification gate. + Quantum Aplitude Amplification gate. - (Quick reference https://en.wikipedia.org/wiki/Amplitude_amplification. - Complete reference G. Brassard, P. Hoyer, M. Mosca, A. Tapp (2000) - Quantum Amplitude Amplification and Estimation - https://arxiv.org/abs/quant-ph/0005055) + (Quick reference https://en.wikipedia.org/wiki/Amplitude_amplification. Complete reference G. Brassard, P. Hoyer, + M. Mosca, A. Tapp (2000) Quantum Amplitude Amplification and Estimation https://arxiv.org/abs/quant-ph/0005055) - Quantum Amplitude Amplification (QAA) executes the algorithm, but not - the final measurement required to obtain the marked state(s) with high - probability. The starting state on wich the QAA algorithm is executed - is the one resulting of aplying the Algorithm on the |0> state. + Quantum Amplitude Amplification (QAA) executes the algorithm, but not the final measurement required to obtain the + marked state(s) with high probability. The starting state on wich the QAA algorithm is executed is the one + resulting of aplying the Algorithm on the |0> state. Example: .. code-block:: python @@ -60,16 +59,14 @@ def func_oracle(eng,system_qubits,qaa_ancilla): All(Measure) | system_qubits Warning: - No qubit allocation/deallocation may take place during the call - to the defined Algorithm :code:`func_algorithm` + No qubit allocation/deallocation may take place during the call to the defined Algorithm + :code:`func_algorithm` Attributes: - func_algorithm: Algorithm that initialite the state and to be used - in the QAA algorithm + func_algorithm: Algorithm that initialite the state and to be used in the QAA algorithm func_oracle: The Oracle that marks the state(s) as "good" system_qubits: the system we are interested on - qaa_ancilla: auxiliary qubit that helps to invert the amplitude of the - "good" states + qaa_ancilla: auxiliary qubit that helps to invert the amplitude of the "good" states """ diff --git a/projectq/ops/_qftgate.py b/projectq/ops/_qftgate.py index 8b22bcc87..45eaec9bf 100755 --- a/projectq/ops/_qftgate.py +++ b/projectq/ops/_qftgate.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Contains the definition of the QFT gate""" + from ._basics import BasicGate diff --git a/projectq/ops/_qpegate.py b/projectq/ops/_qpegate.py index 43834d55f..c806c61a0 100755 --- a/projectq/ops/_qpegate.py +++ b/projectq/ops/_qpegate.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Contains the definition of the quantum phase estimation gate""" + from ._basics import BasicGate diff --git a/projectq/ops/_qubit_operator.py b/projectq/ops/_qubit_operator.py index 621d50d77..19f1301e0 100644 --- a/projectq/ops/_qubit_operator.py +++ b/projectq/ops/_qubit_operator.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """QubitOperator stores a sum of Pauli operators acting on qubits.""" + import cmath import copy @@ -44,7 +45,7 @@ class QubitOperatorError(Exception): - pass + """Exception raised when a QubitOperator is instantiated with some invalid data""" class QubitOperator(BasicGate): @@ -55,63 +56,51 @@ class QubitOperator(BasicGate): coefficent * local_operator[0] x ... x local_operator[n-1] - where x is the tensor product. A local operator is a Pauli operator - ('I', 'X', 'Y', or 'Z') which acts on one qubit. In math notation a term - is, for example, 0.5 * 'X0 X5', which means that a Pauli X operator acts - on qubit 0 and 5, while the identity operator acts on all other qubits. + where x is the tensor product. A local operator is a Pauli operator ('I', 'X', 'Y', or 'Z') which acts on one + qubit. In math notation a term is, for example, 0.5 * 'X0 X5', which means that a Pauli X operator acts on qubit 0 + and 5, while the identity operator acts on all other qubits. - A QubitOperator represents a sum of terms acting on qubits and overloads - operations for easy manipulation of these objects by the user. + A QubitOperator represents a sum of terms acting on qubits and overloads operations for easy manipulation of these + objects by the user. - Note for a QubitOperator to be a Hamiltonian which is a hermitian - operator, the coefficients of all terms must be real. + Note for a QubitOperator to be a Hamiltonian which is a hermitian operator, the coefficients of all terms must be + real. .. code-block:: python hamiltonian = 0.5 * QubitOperator('X0 X5') + 0.3 * QubitOperator('Z0') - Our Simulator takes a hermitian QubitOperator to directly calculate the - expectation value (see Simulator.get_expectation_value) of this observable. + Our Simulator takes a hermitian QubitOperator to directly calculate the expectation value (see + Simulator.get_expectation_value) of this observable. - A hermitian QubitOperator can also be used as input for the - TimeEvolution gate. + A hermitian QubitOperator can also be used as input for the TimeEvolution gate. - If the QubitOperator is unitary, i.e., it contains only one term with a - coefficient, whose absolute value is 1, then one can apply it directly to - qubits: + If the QubitOperator is unitary, i.e., it contains only one term with a coefficient, whose absolute value is 1, + then one can apply it directly to qubits: .. code-block:: python eng = projectq.MainEngine() qureg = eng.allocate_qureg(6) - QubitOperator('X0 X5', 1.j) | qureg # Applies X to qubit 0 and 5 - # with an additional global phase - # of 1.j + QubitOperator('X0 X5', 1.j) | qureg # Applies X to qubit 0 and 5 with an additional global phase of 1.j Attributes: - terms (dict): **key**: A term represented by a tuple containing all - non-trivial local Pauli operators ('X', 'Y', or 'Z'). - A non-trivial local Pauli operator is specified by a - tuple with the first element being an integer - indicating the qubit on which a non-trivial local - operator acts and the second element being a string, - either 'X', 'Y', or 'Z', indicating which non-trivial - Pauli operator acts on that qubit. Examples: - ((1, 'X'),) or ((1, 'X'), (4,'Z')) or the identity (). - The tuples representing the non-trivial local terms - are sorted according to the qubit number they act on, - starting from 0. - **value**: Coefficient of this term as a (complex) float + terms (dict): **key**: A term represented by a tuple containing all non-trivial local Pauli operators ('X', + 'Y', or 'Z'). A non-trivial local Pauli operator is specified by a tuple with the first element + being an integer indicating the qubit on which a non-trivial local operator acts and the second + element being a string, either 'X', 'Y', or 'Z', indicating which non-trivial Pauli operator + acts on that qubit. Examples: ((1, 'X'),) or ((1, 'X'), (4,'Z')) or the identity (). The tuples + representing the non-trivial local terms are sorted according to the qubit number they act on, + starting from 0. **value**: Coefficient of this term as a (complex) float """ - def __init__(self, term=None, coefficient=1.0): + def __init__(self, term=None, coefficient=1.0): # pylint: disable=too-many-branches """ Inits a QubitOperator. - The init function only allows to initialize one term. Additional terms - have to be added using += (which is fast) or using + of two - QubitOperator objects: + The init function only allows to initialize one term. Additional terms have to be added using += (which is + fast) or using + of two QubitOperator objects: Example: .. code-block:: python @@ -123,28 +112,22 @@ def __init__(self, term=None, coefficient=1.0): ham2 += 0.6 * QubitOperator('X0 Y3') Note: - Adding terms to QubitOperator is faster using += (as this is done - by in-place addition). Specifying the coefficient in the __init__ - is faster than by multiplying a QubitOperator with a scalar as - calls an out-of-place multiplication. + Adding terms to QubitOperator is faster using += (as this is done by in-place addition). Specifying the + coefficient in the __init__ is faster than by multiplying a QubitOperator with a scalar as calls an + out-of-place multiplication. Args: - coefficient (complex float, optional): The coefficient of the - first term of this QubitOperator. Default is 1.0. + coefficient (complex float, optional): The coefficient of the first term of this QubitOperator. Default is + 1.0. term (optional, empy tuple, a tuple of tuples, or a string): - 1) Default is None which means there are no terms in the - QubitOperator hence it is the "zero" Operator - 2) An empty tuple means there are no non-trivial Pauli - operators acting on the qubits hence only identities - with a coefficient (which by default is 1.0). - 3) A sorted tuple of tuples. The first element of each tuple - is an integer indicating the qubit on which a non-trivial - local operator acts, starting from zero. The second element - of each tuple is a string, either 'X', 'Y' or 'Z', - indicating which local operator acts on that qubit. - 4) A string of the form 'X0 Z2 Y5', indicating an X on - qubit 0, Z on qubit 2, and Y on qubit 5. The string should - be sorted by the qubit number. '' is the identity. + 1) Default is None which means there are no terms in the QubitOperator hence it is the "zero" Operator + 2) An empty tuple means there are no non-trivial Pauli operators acting on the qubits hence only + identities with a coefficient (which by default is 1.0). + 3) A sorted tuple of tuples. The first element of each tuple is an integer indicating the qubit on + which a non-trivial local operator acts, starting from zero. The second element of each tuple is a + string, either 'X', 'Y' or 'Z', indicating which local operator acts on that qubit. + 4) A string of the form 'X0 Z2 Y5', indicating an X on qubit 0, Z on qubit 2, and Y on qubit 5. The + string should be sorted by the qubit number. '' is the identity. Raises: QubitOperatorError: Invalid operators provided to QubitOperator. @@ -155,7 +138,7 @@ def __init__(self, term=None, coefficient=1.0): self.terms = {} if term is None: return - elif isinstance(term, tuple): + if isinstance(term, tuple): if term == (): self.terms[()] = coefficient else: @@ -176,10 +159,10 @@ def __init__(self, term=None, coefficient=1.0): self.terms[tuple(term)] = coefficient elif isinstance(term, str): list_ops = [] - for el in term.split(): - if len(el) < 2: + for element in term.split(): + if len(element) < 2: raise ValueError('term specified incorrectly.') - list_ops.append((int(el[1:]), el[0])) + list_ops.append((int(element[1:]), element[0])) # Test that list_ops has correct format of tuples for local_operator in list_ops: qubit_num, action = local_operator @@ -195,8 +178,8 @@ def __init__(self, term=None, coefficient=1.0): def compress(self, abs_tol=1e-12): """ - Eliminates all terms with coefficients close to zero and removes - imaginary parts of coefficients that are close to zero. + Eliminates all terms with coefficients close to zero and removes imaginary parts of coefficients that are + close to zero. Args: abs_tol(float): Absolute tolerance, must be at least 0.0 @@ -214,11 +197,9 @@ def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12): """ Returns True if other (QubitOperator) is close to self. - Comparison is done for each term individually. Return True - if the difference between each term in self and other is - less than the relative tolerance w.r.t. either other or self - (symmetric test) or if the difference is less than the absolute - tolerance. + Comparison is done for each term individually. Return True if the difference between each term in self and + other is less than the relative tolerance w.r.t. either other or self (symmetric test) or if the difference is + less than the absolute tolerance. Args: other(QubitOperator): QubitOperator to compare against. @@ -227,10 +208,10 @@ def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12): """ # terms which are in both: for term in set(self.terms).intersection(set(other.terms)): - a = self.terms[term] - b = other.terms[term] + left = self.terms[term] + right = other.terms[term] # math.isclose does this in Python >=3.5 - if not abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol): + if not abs(left - right) <= max(rel_tol * max(abs(left), abs(right)), abs_tol): return False # terms only in one (compare to 0.0 so only abs_tol) for term in set(self.terms).symmetric_difference(set(other.terms)): @@ -241,7 +222,7 @@ def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12): return False return True - def __or__(self, qubits): + def __or__(self, qubits): # pylint: disable=too-many-locals """ Operator| overload which enables the following syntax: @@ -317,7 +298,7 @@ def __or__(self, qubits): # Check that Qureg has enough qubits: num_qubits = len(qubits[0]) non_trivial_qubits = set() - for index, action in term: + for index, _ in term: non_trivial_qubits.add(index) if max(non_trivial_qubits) >= num_qubits: raise ValueError("QubitOperator acts on more qubits than the gate is applied to.") @@ -335,11 +316,10 @@ def __or__(self, qubits): # 0,..., len(non_trivial_qubits) - 1 new_index = dict() non_trivial_qubits = sorted(list(non_trivial_qubits)) - for i in range(len(non_trivial_qubits)): - new_index[non_trivial_qubits[i]] = i + for i, qubit in enumerate(non_trivial_qubits): + new_index[qubit] = i new_qubitoperator = QubitOperator() - assert len(new_qubitoperator.terms) == 0 - new_term = tuple([(new_index[index], action) for index, action in term]) + new_term = tuple((new_index[index], action) for index, action in term) new_qubitoperator.terms[new_term] = coefficient new_qubits = [qubits[0][i] for i in non_trivial_qubits] # Apply new gate @@ -373,10 +353,9 @@ def get_merged(self, other): """ if isinstance(other, self.__class__) and len(other.terms) == 1 and len(self.terms) == 1: return self * other - else: - raise NotMergeable() + raise NotMergeable() - def __imul__(self, multiplier): + def __imul__(self, multiplier): # pylint: disable=too-many-locals,too-many-branches """ In-place multiply (*=) terms with scalar or QubitOperator. @@ -390,7 +369,7 @@ def __imul__(self, multiplier): return self # Handle QubitOperator. - elif isinstance(multiplier, QubitOperator): + if isinstance(multiplier, QubitOperator): # pylint: disable=too-many-nested-blocks result_terms = dict() for left_term in self.terms: for right_term in multiplier.terms: @@ -442,8 +421,7 @@ def __imul__(self, multiplier): result_terms[tmp_key] = new_coefficient self.terms = result_terms return self - else: - raise TypeError('Cannot in-place multiply term of invalid type ' + 'to QubitTerm.') + raise TypeError('Cannot in-place multiply term of invalid type ' + 'to QubitTerm.') def __mul__(self, multiplier): """ @@ -458,12 +436,11 @@ def __mul__(self, multiplier): Raises: TypeError: Invalid type cannot be multiply with QubitOperator. """ - if isinstance(multiplier, (int, float, complex)) or isinstance(multiplier, QubitOperator): + if isinstance(multiplier, (int, float, complex, QubitOperator)): product = copy.deepcopy(self) product *= multiplier return product - else: - raise TypeError('Object of invalid type cannot multiply with QubitOperator.') + raise TypeError('Object of invalid type cannot multiply with QubitOperator.') def __rmul__(self, multiplier): """ @@ -587,9 +564,10 @@ def __str__(self): tmp_string += ' X{}'.format(operator[0]) elif operator[1] == 'Y': tmp_string += ' Y{}'.format(operator[0]) - else: - assert operator[1] == 'Z' + elif operator[1] == 'Z': tmp_string += ' Z{}'.format(operator[0]) + else: # pragma: no cover + raise ValueError('Internal compiler error: operator must be one of X, Y, Z!') string_rep += '{} +\n'.format(tmp_string) return string_rep[:-3] diff --git a/projectq/ops/_shortcuts.py b/projectq/ops/_shortcuts.py index 0e06586cb..0defb4e22 100755 --- a/projectq/ops/_shortcuts.py +++ b/projectq/ops/_shortcuts.py @@ -25,9 +25,9 @@ def CRz(angle): """ - Shortcut for C(Rz(angle), n=1). + Shortcut for C(Rz(angle), n_qubits=1). """ - return C(Rz(angle), n=1) + return C(Rz(angle), n_qubits=1) CNOT = CX = C(NOT) diff --git a/projectq/ops/_state_prep.py b/projectq/ops/_state_prep.py index af6dce015..d86824bf8 100644 --- a/projectq/ops/_state_prep.py +++ b/projectq/ops/_state_prep.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Contains the definition of the state preparation gate""" + from ._basics import BasicGate diff --git a/projectq/ops/_time_evolution.py b/projectq/ops/_time_evolution.py index d80d7388b..80e051f70 100644 --- a/projectq/ops/_time_evolution.py +++ b/projectq/ops/_time_evolution.py @@ -13,17 +13,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Contains the definition of the time evolution gate""" + import copy -from projectq.ops import Ph from ._basics import BasicGate, NotMergeable -from ._qubit_operator import QubitOperator from ._command import apply_command +from ._gates import Ph +from ._qubit_operator import QubitOperator class NotHermitianOperatorError(Exception): - pass + """Error raised if an operator is non-hermitian""" class TimeEvolution(BasicGate): @@ -54,8 +56,7 @@ def __init__(self, time, hamiltonian): Initialize time evolution gate. Note: - The hamiltonian must be hermitian and therefore only terms with - real coefficients are allowed. + The hamiltonian must be hermitian and therefore only terms with real coefficients are allowed. Coefficients are internally converted to float. Args: @@ -63,10 +64,8 @@ def __init__(self, time, hamiltonian): hamiltonian (QubitOperator): hamiltonian to evolve under. Raises: - TypeError: If time is not a numeric type and hamiltonian is not a - QubitOperator. - NotHermitianOperatorError: If the input hamiltonian is not - hermitian (only real coefficients). + TypeError: If time is not a numeric type and hamiltonian is not a QubitOperator. + NotHermitianOperatorError: If the input hamiltonian is not hermitian (only real coefficients). """ BasicGate.__init__(self) if not isinstance(time, (float, int)): @@ -93,27 +92,23 @@ def get_merged(self, other): Two TimeEvolution gates are merged if: 1) both have the same terms - 2) the proportionality factor for each of the terms - must have relative error <= 1e-9 compared to the + 2) the proportionality factor for each of the terms must have relative error <= 1e-9 compared to the proportionality factors of the other terms. Note: - While one could merge gates for which both hamiltonians commute, - we are not doing this as in general the resulting gate would have - to be decomposed again. + While one could merge gates for which both hamiltonians commute, we are not doing this as in general the + resulting gate would have to be decomposed again. Note: - We are not comparing if terms are proportional to each other with - an absolute tolerance. It is up to the user to remove terms close - to zero because we cannot choose a suitable absolute error which - works for everyone. Use, e.g., a decomposition rule for that. + We are not comparing if terms are proportional to each other with an absolute tolerance. It is up to the + user to remove terms close to zero because we cannot choose a suitable absolute error which works for + everyone. Use, e.g., a decomposition rule for that. Args: other: TimeEvolution gate Raises: - NotMergeable: If the other gate is not a TimeEvolution gate or - hamiltonians are not suitable for merging. + NotMergeable: If the other gate is not a TimeEvolution gate or hamiltonians are not suitable for merging. Returns: New TimeEvolution gate equivalent to the two merged gates. @@ -131,8 +126,7 @@ def get_merged(self, other): # Terms are proportional to each other new_time = self.time + other.time / factor return TimeEvolution(time=new_time, hamiltonian=self.hamiltonian) - else: - raise NotMergeable("Cannot merge these two gates.") + raise NotMergeable("Cannot merge these two gates.") def __or__(self, qubits): """ @@ -145,8 +139,7 @@ def __or__(self, qubits): TimeEvolution(...) | qubit TimeEvolution(...) | (qubit,) - Unlike other gates, this gate is only allowed to be applied to one - quantum register or one qubit. + Unlike other gates, this gate is only allowed to be applied to one quantum register or one qubit. Example: @@ -156,11 +149,10 @@ def __or__(self, qubits): hamiltonian = QubitOperator("X1 Y3", 0.5) TimeEvolution(time=2.0, hamiltonian=hamiltonian) | wavefunction - While in the above example the TimeEvolution gate is applied to 5 - qubits, the hamiltonian of this TimeEvolution gate acts only - non-trivially on the two qubits wavefunction[1] and wavefunction[3]. - Therefore, the operator| will rescale the indices in the hamiltonian - and sends the equivalent of the following new gate to the MainEngine: + While in the above example the TimeEvolution gate is applied to 5 qubits, the hamiltonian of this + TimeEvolution gate acts only non-trivially on the two qubits wavefunction[1] and wavefunction[3]. Therefore, + the operator| will rescale the indices in the hamiltonian and sends the equivalent of the following new gate + to the MainEngine: .. code-block:: python @@ -170,8 +162,8 @@ def __or__(self, qubits): which is only a two qubit gate. Args: - qubits: one Qubit object, one list of Qubit objects, one Qureg - object, or a tuple of the former three cases. + qubits: one Qubit object, one list of Qubit objects, one Qureg object, or a tuple of the former three + cases. """ # Check that input is only one qureg or one qubit qubits = self.make_tuple_of_qureg(qubits) @@ -185,7 +177,7 @@ def __or__(self, qubits): num_qubits = len(qubits[0]) non_trivial_qubits = set() for term in self.hamiltonian.terms: - for index, action in term: + for index, _ in term: non_trivial_qubits.add(index) if max(non_trivial_qubits) >= num_qubits: raise ValueError("hamiltonian acts on more qubits than the gate is applied to.") @@ -194,12 +186,11 @@ def __or__(self, qubits): # 0,...,len(non_trivial_qubits) - 1 new_index = dict() non_trivial_qubits = sorted(list(non_trivial_qubits)) - for i in range(len(non_trivial_qubits)): - new_index[non_trivial_qubits[i]] = i + for i, qubit in enumerate(non_trivial_qubits): + new_index[qubit] = i new_hamiltonian = QubitOperator() - assert len(new_hamiltonian.terms) == 0 for term in self.hamiltonian.terms: - new_term = tuple([(new_index[index], action) for index, action in term]) + new_term = tuple((new_index[index], action) for index, action in term) new_hamiltonian.terms[new_term] = self.hamiltonian.terms[term] new_gate = TimeEvolution(time=self.time, hamiltonian=new_hamiltonian) new_qubits = [qubits[0][i] for i in non_trivial_qubits] diff --git a/projectq/ops/_uniformly_controlled_rotation.py b/projectq/ops/_uniformly_controlled_rotation.py index e8d8ccaee..c5ae74229 100644 --- a/projectq/ops/_uniformly_controlled_rotation.py +++ b/projectq/ops/_uniformly_controlled_rotation.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Contains uniformly controlled rotation gates""" + import math from ._basics import ANGLE_PRECISION, ANGLE_TOLERANCE, BasicGate, NotMergeable @@ -22,11 +24,9 @@ class UniformlyControlledRy(BasicGate): """ Uniformly controlled Ry gate as introduced in arXiv:quant-ph/0312218. - This is an n-qubit gate. There are n-1 control qubits and one target qubit. - This gate applies Ry(angles(k)) to the target qubit if the n-1 control - qubits are in the classical state k. As there are 2^(n-1) classical - states for the control qubits, this gate requires 2^(n-1) (potentially - different) angle parameters. + This is an n-qubit gate. There are n-1 control qubits and one target qubit. This gate applies Ry(angles(k)) to + the target qubit if the n-1 control qubits are in the classical state k. As there are 2^(n-1) classical states for + the control qubits, this gate requires 2^(n-1) (potentially different) angle parameters. Example: .. code-block:: python @@ -36,15 +36,13 @@ class UniformlyControlledRy(BasicGate): UniformlyControlledRy(angles=[0.1, 0.2, 0.3, 0.4]) | (controls, target) Note: - The first quantum register contains the control qubits. When converting - the classical state k of the control qubits to an integer, we define - controls[0] to be the least significant (qu)bit. controls can also - be an empty list in which case the gate corresponds to an Ry. + The first quantum register contains the control qubits. When converting the classical state k of the control + qubits to an integer, we define controls[0] to be the least significant (qu)bit. controls can also be an empty + list in which case the gate corresponds to an Ry. Args: - angles(list[float]): Rotation angles. Ry(angles[k]) is applied - conditioned on the control qubits being in state - k. + angles(list[float]): Rotation angles. Ry(angles[k]) is applied conditioned on the control qubits being in + state k. """ def __init__(self, angles): @@ -73,8 +71,7 @@ def __eq__(self, other): """Return True if same class, same rotation angles.""" if isinstance(other, self.__class__): return self.angles == other.angles - else: - return False + return False def __ne__(self, other): return not self.__eq__(other) @@ -87,11 +84,9 @@ class UniformlyControlledRz(BasicGate): """ Uniformly controlled Rz gate as introduced in arXiv:quant-ph/0312218. - This is an n-qubit gate. There are n-1 control qubits and one target qubit. - This gate applies Rz(angles(k)) to the target qubit if the n-1 control - qubits are in the classical state k. As there are 2^(n-1) classical - states for the control qubits, this gate requires 2^(n-1) (potentially - different) angle parameters. + This is an n-qubit gate. There are n-1 control qubits and one target qubit. This gate applies Rz(angles(k)) to + the target qubit if the n-1 control qubits are in the classical state k. As there are 2^(n-1) classical states for + the control qubits, this gate requires 2^(n-1) (potentially different) angle parameters. Example: .. code-block:: python @@ -101,10 +96,9 @@ class UniformlyControlledRz(BasicGate): UniformlyControlledRz(angles=[0.1, 0.2, 0.3, 0.4]) | (controls, target) Note: - The first quantum register are the contains qubits. When converting - the classical state k of the control qubits to an integer, we define - controls[0] to be the least significant (qu)bit. controls can also - be an empty list in which case the gate corresponds to an Rz. + The first quantum register are the contains qubits. When converting the classical state k of the control + qubits to an integer, we define controls[0] to be the least significant (qu)bit. controls can also be an empty + list in which case the gate corresponds to an Rz. Args: angles(list[float]): Rotation angles. Rz(angles[k]) is applied @@ -113,7 +107,7 @@ class UniformlyControlledRz(BasicGate): """ def __init__(self, angles): - BasicGate.__init__(self) + super().__init__() rounded_angles = [] for angle in angles: new_angle = round(float(angle) % (4.0 * math.pi), ANGLE_PRECISION) @@ -138,8 +132,7 @@ def __eq__(self, other): """Return True if same class, same rotation angles.""" if isinstance(other, self.__class__): return self.angles == other.angles - else: - return False + return False def __ne__(self, other): return not self.__eq__(other) diff --git a/projectq/setups/__init__.py b/projectq/setups/__init__.py index 16fc4afdf..f279b3d1d 100755 --- a/projectq/setups/__init__.py +++ b/projectq/setups/__init__.py @@ -12,3 +12,5 @@ # 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. + +"""ProjectQ module containing the basic setups for ProjectQ as well as the decomposition rules""" diff --git a/projectq/setups/_utils.py b/projectq/setups/_utils.py new file mode 100644 index 000000000..a50110ec1 --- /dev/null +++ b/projectq/setups/_utils.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# 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. +""" +Some utility functions common to some setups +""" +import inspect + +from projectq.cengines import ( + AutoReplacer, + DecompositionRuleSet, + InstructionFilter, + LocalOptimizer, + TagRemover, +) +from projectq.ops import ClassicalInstructionGate, CNOT, ControlledGate, Swap, QFT, get_inverse, BasicMathGate +import projectq.libs.math +import projectq.setups.decompositions + + +def one_and_two_qubit_gates(eng, cmd): # pylint: disable=unused-argument + """ + Filter out 1- and 2-qubit gates. + """ + all_qubits = [qb for qureg in cmd.all_qubits for qb in qureg] + if isinstance(cmd.gate, ClassicalInstructionGate): + # This is required to allow Measure, Allocate, Deallocate, Flush + return True + if eng.next_engine.is_available(cmd): + return True + if len(all_qubits) <= 2: + return True + return False + + +def high_level_gates(eng, cmd): # pylint: disable=unused-argument + """ + Remove any MathGates. + """ + gate = cmd.gate + if eng.next_engine.is_available(cmd): + return True + if gate == QFT or get_inverse(gate) == QFT or gate == Swap: + return True + if isinstance(gate, BasicMathGate): + return False + return True + + +def get_engine_list_linear_grid_base(mapper, one_qubit_gates="any", two_qubit_gates=(CNOT, Swap)): + """ + Returns an engine list to compile to a 2-D grid of qubits. + + Note: + If you choose a new gate set for which the compiler does not yet have standard rules, it raises an + `NoGateDecompositionError` or a `RuntimeError: maximum recursion depth exceeded...`. Also note that even the + gate sets which work might not yet be optimized. So make sure to double check and potentially extend the + decomposition rules. This implemention currently requires that the one qubit gates must contain Rz and at + least one of {Ry(best), Rx, H} and the two qubit gate must contain CNOT (recommended) or CZ. + + Note: + Classical instructions gates such as e.g. Flush and Measure are automatically allowed. + + Example: + get_engine_list(num_rows=2, num_columns=3, + one_qubit_gates=(Rz, Ry, Rx, H), + two_qubit_gates=(CNOT,)) + + Args: + num_rows(int): Number of rows in the grid + num_columns(int): Number of columns in the grid. + one_qubit_gates: "any" allows any one qubit gate, otherwise provide a tuple of the allowed gates. If the gates + are instances of a class (e.g. X), it allows all gates which are equal to it. If the gate is + a class (Rz), it allows all instances of this class. Default is "any" + two_qubit_gates: "any" allows any two qubit gate, otherwise provide a tuple of the allowed gates. If the gates + are instances of a class (e.g. CNOT), it allows all gates which are equal to it. If the gate + is a class, it allows all instances of this class. Default is (CNOT, Swap). + Raises: + TypeError: If input is for the gates is not "any" or a tuple. + + Returns: + A list of suitable compiler engines. + """ + if two_qubit_gates != "any" and not isinstance(two_qubit_gates, tuple): + raise TypeError( + "two_qubit_gates parameter must be 'any' or a tuple. When supplying only one gate, make sure to" + "correctly create the tuple (don't miss the comma), e.g. two_qubit_gates=(CNOT,)" + ) + if one_qubit_gates != "any" and not isinstance(one_qubit_gates, tuple): + raise TypeError("one_qubit_gates parameter must be 'any' or a tuple.") + + rule_set = DecompositionRuleSet(modules=[projectq.libs.math, projectq.setups.decompositions]) + allowed_gate_classes = [] + allowed_gate_instances = [] + if one_qubit_gates != "any": + for gate in one_qubit_gates: + if inspect.isclass(gate): + allowed_gate_classes.append(gate) + else: + allowed_gate_instances.append((gate, 0)) + if two_qubit_gates != "any": + for gate in two_qubit_gates: + if inspect.isclass(gate): + # Controlled gate classes don't yet exists and would require + # separate treatment + if isinstance(gate, ControlledGate): # pragma: no cover + raise RuntimeError('Support for controlled gate not implemented!') + allowed_gate_classes.append(gate) + else: + if isinstance(gate, ControlledGate): + allowed_gate_instances.append((gate._gate, gate._n)) # pylint: disable=protected-access + else: + allowed_gate_instances.append((gate, 0)) + allowed_gate_classes = tuple(allowed_gate_classes) + allowed_gate_instances = tuple(allowed_gate_instances) + + def low_level_gates(eng, cmd): # pylint: disable=unused-argument + all_qubits = [q for qr in cmd.all_qubits for q in qr] + if len(all_qubits) > 2: # pragma: no cover + raise ValueError('Filter function cannot handle gates with more than 2 qubits!') + if isinstance(cmd.gate, ClassicalInstructionGate): + # This is required to allow Measure, Allocate, Deallocate, Flush + return True + if one_qubit_gates == "any" and len(all_qubits) == 1: + return True + if two_qubit_gates == "any" and len(all_qubits) == 2: + return True + if isinstance(cmd.gate, allowed_gate_classes): + return True + if (cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances: + return True + return False + + return [ + AutoReplacer(rule_set), + TagRemover(), + InstructionFilter(high_level_gates), + LocalOptimizer(5), + AutoReplacer(rule_set), + TagRemover(), + InstructionFilter(one_and_two_qubit_gates), + LocalOptimizer(5), + mapper, + AutoReplacer(rule_set), + TagRemover(), + InstructionFilter(low_level_gates), + LocalOptimizer(5), + ] diff --git a/projectq/setups/aqt.py b/projectq/setups/aqt.py index 78849dc98..bd4ff862e 100644 --- a/projectq/setups/aqt.py +++ b/projectq/setups/aqt.py @@ -31,6 +31,9 @@ def get_engine_list(token=None, device=None): + """ + Return the default list of compiler engine for the AQT plaftorm + """ # Access to the hardware properties via show_devices # Can also be extended to take into account gate fidelities, new available # gate, etc.. @@ -65,8 +68,8 @@ def get_engine_list(token=None, device=None): class DeviceOfflineError(Exception): - pass + """Exception raised if a selected device is currently offline""" class DeviceNotHandledError(Exception): - pass + """Exception raised if a selected device is cannot handle the circuit""" diff --git a/projectq/setups/awsbraket.py b/projectq/setups/awsbraket.py index c5ec54a12..e9558a1d1 100644 --- a/projectq/setups/awsbraket.py +++ b/projectq/setups/awsbraket.py @@ -45,6 +45,9 @@ def get_engine_list(credentials=None, device=None): + """ + Return the default list of compiler engine for the AWS Braket platform. + """ # Access to the hardware properties via show_devices # Can also be extended to take into account gate fidelities, new available # gate, etc.. @@ -81,7 +84,8 @@ def get_engine_list(credentials=None, device=None): other_gates=(Barrier,), ) return setup + raise RuntimeError('Unsupported device type: {}!'.format(device)) # pragma: no cover class DeviceOfflineError(Exception): - pass + """Exception raised if a selected device is currently offline""" diff --git a/projectq/setups/decompositions/amplitudeamplification.py b/projectq/setups/decompositions/amplitudeamplification.py index 6472639db..7e2a171b0 100644 --- a/projectq/setups/decompositions/amplitudeamplification.py +++ b/projectq/setups/decompositions/amplitudeamplification.py @@ -77,7 +77,7 @@ def func_oracle(eng,system_qubits,qaa_ancilla): from projectq.ops import QAA -def _decompose_QAA(cmd): +def _decompose_QAA(cmd): # pylint: disable=invalid-name """Decompose the Quantum Amplitude Apmplification algorithm as a gate.""" eng = cmd.engine @@ -86,24 +86,24 @@ def _decompose_QAA(cmd): qaa_ancilla = cmd.qubits[1] # The Oracle and the Algorithm - Oracle = cmd.gate.oracle - A = cmd.gate.algorithm + oracle = cmd.gate.oracle + alg = cmd.gate.algorithm # Apply the oracle to invert the amplitude of the good states, S_Chi - Oracle(eng, system_qubits, qaa_ancilla) + oracle(eng, system_qubits, qaa_ancilla) # Apply the inversion of the Algorithm, # the inversion of the aplitude of |0> and the Algorithm with Compute(eng): with Dagger(eng): - A(eng, system_qubits) + alg(eng, system_qubits) All(X) | system_qubits with Control(eng, system_qubits[0:-1]): Z | system_qubits[-1] with CustomUncompute(eng): All(X) | system_qubits - A(eng, system_qubits) + alg(eng, system_qubits) Ph(math.pi) | system_qubits[0] diff --git a/projectq/setups/decompositions/arb1qubit2rzandry.py b/projectq/setups/decompositions/arb1qubit2rzandry.py index ba20c98a7..fadc006d2 100644 --- a/projectq/setups/decompositions/arb1qubit2rzandry.py +++ b/projectq/setups/decompositions/arb1qubit2rzandry.py @@ -48,16 +48,12 @@ def _recognize_arb1qubit(cmd): carb1qubit2cnotrzandry instead. """ try: - m = cmd.gate.matrix - if len(m) == 2 and get_control_count(cmd) == 0: - return True - else: - return False + return len(cmd.gate.matrix) == 2 and get_control_count(cmd) == 0 except AttributeError: return False -def _test_parameters(matrix, a, b_half, c_half, d_half): +def _test_parameters(matrix, a, b_half, c_half, d_half): # pylint: disable=invalid-name """ It builds matrix U with parameters (a, b/2, c/2, d/2) and compares against matrix. @@ -75,7 +71,7 @@ def _test_parameters(matrix, a, b_half, c_half, d_half): Returns: True if matrix elements of U and `matrix` are TOLERANCE close. """ - U = [ + unitary = [ [ cmath.exp(1j * (a - b_half - d_half)) * math.cos(c_half), -cmath.exp(1j * (a - b_half + d_half)) * math.sin(c_half), @@ -85,10 +81,10 @@ def _test_parameters(matrix, a, b_half, c_half, d_half): cmath.exp(1j * (a + b_half + d_half)) * math.cos(c_half), ], ] - return numpy.allclose(U, matrix, rtol=10 * TOLERANCE, atol=TOLERANCE) + return numpy.allclose(unitary, matrix, rtol=10 * TOLERANCE, atol=TOLERANCE) -def _find_parameters(matrix): +def _find_parameters(matrix): # pylint: disable=too-many-branches,too-many-statements """ Given a 2x2 unitary matrix, find the parameters a, b/2, c/2, and d/2 such that @@ -114,11 +110,11 @@ def _find_parameters(matrix): # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, # w.l.g. we can choose a==0 because (see U above) # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. - a = 0 + a = 0 # pylint: disable=invalid-name else: - a = two_a / 2.0 + a = two_a / 2.0 # pylint: disable=invalid-name d_half = 0 # w.l.g - b = cmath.phase(matrix[1][1]) - cmath.phase(matrix[0][0]) + b = cmath.phase(matrix[1][1]) - cmath.phase(matrix[0][0]) # pylint: disable=invalid-name possible_b_half = [ (b / 2.0) % (2 * math.pi), (b / 2.0 + math.pi) % (2 * math.pi), @@ -143,11 +139,11 @@ def _find_parameters(matrix): # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, # w.l.g. we can choose a==0 because (see U above) # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. - a = 0 + a = 0 # pylint: disable=invalid-name else: - a = two_a / 2.0 + a = two_a / 2.0 # pylint: disable=invalid-name d_half = 0 # w.l.g - b = cmath.phase(matrix[1][0]) - cmath.phase(matrix[0][1]) + math.pi + b = cmath.phase(matrix[1][0]) - cmath.phase(matrix[0][1]) + math.pi # pylint: disable=invalid-name possible_b_half = [ (b / 2.0) % (2 * math.pi), (b / 2.0 + math.pi) % (2 * math.pi), @@ -172,9 +168,9 @@ def _find_parameters(matrix): # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, # w.l.g. we can choose a==0 because (see U above) # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. - a = 0 + a = 0 # pylint: disable=invalid-name else: - a = two_a / 2.0 + a = two_a / 2.0 # pylint: disable=invalid-name two_d = 2.0 * cmath.phase(matrix[0][1]) - 2.0 * cmath.phase(matrix[0][0]) # yapf: disable possible_d_half = [two_d/4. % (2*math.pi), @@ -219,7 +215,7 @@ def _decompose_arb1qubit(cmd): we can choose a = 0. """ matrix = cmd.gate.matrix.tolist() - a, b_half, c_half, d_half = _find_parameters(matrix) + a, b_half, c_half, d_half = _find_parameters(matrix) # pylint: disable=invalid-name qb = cmd.qubits eng = cmd.engine with Control(eng, cmd.control_qubits): diff --git a/projectq/setups/decompositions/barrier.py b/projectq/setups/decompositions/barrier.py index 5f4c05439..f3e94f408 100755 --- a/projectq/setups/decompositions/barrier.py +++ b/projectq/setups/decompositions/barrier.py @@ -22,12 +22,11 @@ from projectq.ops import BarrierGate -def _decompose_barrier(cmd): +def _decompose_barrier(cmd): # pylint: disable=unused-argument """Throw out all barriers if they are not supported.""" - pass -def _recognize_barrier(cmd): +def _recognize_barrier(cmd): # pylint: disable=unused-argument """Recognize all barriers.""" return True diff --git a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py index e0fb14780..b78d20556 100644 --- a/projectq/setups/decompositions/carb1qubit2cnotrzandry.py +++ b/projectq/setups/decompositions/carb1qubit2cnotrzandry.py @@ -38,15 +38,13 @@ def _recognize_carb1qubit(cmd): """Recognize single controlled one qubit gates with a matrix.""" if get_control_count(cmd) == 1: try: - m = cmd.gate.matrix - if len(m) == 2: - return True + return len(cmd.gate.matrix) == 2 except AttributeError: return False return False -def _test_parameters(matrix, a, b, c_half): +def _test_parameters(matrix, a, b, c_half): # pylint: disable=invalid-name """ It builds matrix V with parameters (a, b, c/2) and compares against matrix. @@ -63,7 +61,7 @@ def _test_parameters(matrix, a, b, c_half): Returns: True if matrix elements of V and `matrix` are TOLERANCE close. """ - V = [ + v_matrix = [ [ -math.sin(c_half) * cmath.exp(1j * a), cmath.exp(1j * (a - b)) * math.cos(c_half), @@ -73,10 +71,10 @@ def _test_parameters(matrix, a, b, c_half): cmath.exp(1j * a) * math.sin(c_half), ], ] - return numpy.allclose(V, matrix, rtol=10 * TOLERANCE, atol=TOLERANCE) + return numpy.allclose(v_matrix, matrix, rtol=10 * TOLERANCE, atol=TOLERANCE) -def _recognize_v(matrix): +def _recognize_v(matrix): # pylint: disable=too-many-branches """ Recognizes a matrix which can be written in the following form: @@ -94,71 +92,65 @@ def _recognize_v(matrix): # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, # w.l.g. we can choose a==0 because (see U above) # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. - a = 0 + a = 0 # pylint: disable=invalid-name else: - a = two_a / 2.0 + a = two_a / 2.0 # pylint: disable=invalid-name two_b = cmath.phase(matrix[1][0]) - cmath.phase(matrix[0][1]) possible_b = [ (two_b / 2.0) % (2 * math.pi), (two_b / 2.0 + math.pi) % (2 * math.pi), ] possible_c_half = [0, math.pi] - found = False - for b, c_half in itertools.product(possible_b, possible_c_half): + + for b, c_half in itertools.product(possible_b, possible_c_half): # pylint: disable=invalid-name if _test_parameters(matrix, a, b, c_half): - found = True - break - assert found # It should work for all matrices with matrix[0][0]==0. - return (a, b, c_half) + return (a, b, c_half) + raise RuntimeError('Case matrix[0][0]==0 should work in all cases, but did not!') # pragma: no cover - elif abs(matrix[0][1]) < TOLERANCE: + if abs(matrix[0][1]) < TOLERANCE: two_a = cmath.phase(-matrix[0][0] * matrix[1][1]) % (2 * math.pi) if abs(two_a) < TOLERANCE or abs(two_a) > 2 * math.pi - TOLERANCE: # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, # w.l.g. we can choose a==0 because (see U above) # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. - a = 0 + a = 0 # pylint: disable=invalid-name else: - a = two_a / 2.0 - b = 0 + a = two_a / 2.0 # pylint: disable=invalid-name + b = 0 # pylint: disable=invalid-name possible_c_half = [math.pi / 2.0, 3.0 / 2.0 * math.pi] - found = False + for c_half in possible_c_half: if _test_parameters(matrix, a, b, c_half): - found = True return (a, b, c_half) return False + two_a = cmath.phase(-1.0 * matrix[0][0] * matrix[1][1]) % (2 * math.pi) + if abs(two_a) < TOLERANCE or abs(two_a) > 2 * math.pi - TOLERANCE: + # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, + # w.l.g. we can choose a==0 because (see U above) + # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. + a = 0 # pylint: disable=invalid-name else: - two_a = cmath.phase(-1.0 * matrix[0][0] * matrix[1][1]) % (2 * math.pi) - if abs(two_a) < TOLERANCE or abs(two_a) > 2 * math.pi - TOLERANCE: - # from 2a==0 (mod 2pi), it follows that a==0 or a==pi, - # w.l.g. we can choose a==0 because (see U above) - # c/2 -> c/2 + pi would have the same effect as as a==0 -> a==pi. - a = 0 - else: - a = two_a / 2.0 - two_b = cmath.phase(matrix[1][0]) - cmath.phase(matrix[0][1]) - possible_b = [ - (two_b / 2.0) % (2 * math.pi), - (two_b / 2.0 + math.pi) % (2 * math.pi), - ] - tmp = math.acos(abs(matrix[1][0])) - # yapf: disable - possible_c_half = [tmp % (2*math.pi), - (tmp+math.pi) % (2*math.pi), - (-1.*tmp) % (2*math.pi), - (-1.*tmp+math.pi) % (2*math.pi)] - # yapf: enable - found = False - for b, c_half in itertools.product(possible_b, possible_c_half): - if _test_parameters(matrix, a, b, c_half): - found = True - return (a, b, c_half) - return False + a = two_a / 2.0 # pylint: disable=invalid-name + two_b = cmath.phase(matrix[1][0]) - cmath.phase(matrix[0][1]) + possible_b = [ + (two_b / 2.0) % (2 * math.pi), + (two_b / 2.0 + math.pi) % (2 * math.pi), + ] + tmp = math.acos(abs(matrix[1][0])) + # yapf: disable + possible_c_half = [tmp % (2*math.pi), + (tmp+math.pi) % (2*math.pi), + (-1.*tmp) % (2*math.pi), + (-1.*tmp+math.pi) % (2*math.pi)] + # yapf: enable + for b, c_half in itertools.product(possible_b, possible_c_half): # pylint: disable=invalid-name + if _test_parameters(matrix, a, b, c_half): + return (a, b, c_half) + return False -def _decompose_carb1qubit(cmd): +def _decompose_carb1qubit(cmd): # pylint: disable=too-many-branches """ Decompose the single controlled 1 qubit gate into CNOT, Rz, Ry, C(Ph). @@ -195,7 +187,7 @@ def _decompose_carb1qubit(cmd): # Case 1: Unitary matrix which can be written in the form of V: parameters_for_v = _recognize_v(matrix) if parameters_for_v: - a, b, c_half = parameters_for_v + a, b, c_half = parameters_for_v # pylint: disable=invalid-name if Rz(-b) != Rz(0): Rz(-b) | qb if Ry(-c_half) != Ry(0): @@ -212,9 +204,9 @@ def _decompose_carb1qubit(cmd): # Case 2: General matrix U: else: - a, b_half, c_half, d_half = arb1q._find_parameters(matrix) - d = 2 * d_half - b = 2 * b_half + a, b_half, c_half, d_half = arb1q._find_parameters(matrix) # pylint: disable=invalid-name, protected-access + d = 2 * d_half # pylint: disable=invalid-name + b = 2 * b_half # pylint: disable=invalid-name if Rz((d - b) / 2.0) != Rz(0): Rz((d - b) / 2.0) | qb with Control(eng, cmd.control_qubits): diff --git a/projectq/setups/decompositions/cnot2rxx.py b/projectq/setups/decompositions/cnot2rxx.py index 92b3a0598..1a37ff045 100644 --- a/projectq/setups/decompositions/cnot2rxx.py +++ b/projectq/setups/decompositions/cnot2rxx.py @@ -20,13 +20,14 @@ Registers a decomposition to for a CNOT gate in terms of Rxx, Rx and Ry gates. """ +import math + from projectq.cengines import DecompositionRule from projectq.meta import get_control_count from projectq.ops import Ph, Rxx, Ry, Rx, X -import math -def _decompose_cnot2rxx_M(cmd): +def _decompose_cnot2rxx_M(cmd): # pylint: disable=invalid-name """Decompose CNOT gate into Rxx gate.""" # Labelled 'M' for 'minus' because decomposition ends with a Ry(-pi/2) ctrl = cmd.control_qubits @@ -38,7 +39,7 @@ def _decompose_cnot2rxx_M(cmd): Ry(-1 * math.pi / 2) | ctrl[0] -def _decompose_cnot2rxx_P(cmd): +def _decompose_cnot2rxx_P(cmd): # pylint: disable=invalid-name """Decompose CNOT gate into Rxx gate.""" # Labelled 'P' for 'plus' because decomposition ends with a Ry(+pi/2) ctrl = cmd.control_qubits diff --git a/projectq/setups/decompositions/cnu2toffoliandcu.py b/projectq/setups/decompositions/cnu2toffoliandcu.py index faedeba56..fd1d0e2b5 100644 --- a/projectq/setups/decompositions/cnu2toffoliandcu.py +++ b/projectq/setups/decompositions/cnu2toffoliandcu.py @@ -25,7 +25,7 @@ from projectq.ops import BasicGate, Toffoli, XGate -def _recognize_CnU(cmd): +def _recognize_CnU(cmd): # pylint: disable=invalid-name """ Recognize an arbitrary gate which has n>=2 control qubits, except a Toffoli gate. @@ -38,7 +38,7 @@ def _recognize_CnU(cmd): return False -def _decompose_CnU(cmd): +def _decompose_CnU(cmd): # pylint: disable=invalid-name """ Decompose a multi-controlled gate U with n control qubits into a single- controlled U. @@ -50,16 +50,16 @@ def _decompose_CnU(cmd): qubits = cmd.qubits ctrl_qureg = cmd.control_qubits gate = cmd.gate - n = get_control_count(cmd) + n_controls = get_control_count(cmd) # specialized for X-gate - if gate == XGate() and n > 2: - n -= 1 - ancilla_qureg = eng.allocate_qureg(n - 1) + if gate == XGate() and n_controls > 2: + n_controls -= 1 + ancilla_qureg = eng.allocate_qureg(n_controls - 1) with Compute(eng): Toffoli | (ctrl_qureg[0], ctrl_qureg[1], ancilla_qureg[0]) - for ctrl_index in range(2, n): + for ctrl_index in range(2, n_controls): Toffoli | ( ctrl_qureg[ctrl_index], ancilla_qureg[ctrl_index - 2], diff --git a/projectq/setups/decompositions/crz2cxandrz.py b/projectq/setups/decompositions/crz2cxandrz.py index 98e2f342e..013fdb978 100755 --- a/projectq/setups/decompositions/crz2cxandrz.py +++ b/projectq/setups/decompositions/crz2cxandrz.py @@ -23,20 +23,20 @@ from projectq.ops import NOT, Rz, C -def _decompose_CRz(cmd): +def _decompose_CRz(cmd): # pylint: disable=invalid-name """Decompose the controlled Rz gate (into CNOT and Rz).""" qubit = cmd.qubits[0] ctrl = cmd.control_qubits gate = cmd.gate - n = get_control_count(cmd) + n_controls = get_control_count(cmd) Rz(0.5 * gate.angle) | qubit - C(NOT, n) | (ctrl, qubit) + C(NOT, n_controls) | (ctrl, qubit) Rz(-0.5 * gate.angle) | qubit - C(NOT, n) | (ctrl, qubit) + C(NOT, n_controls) | (ctrl, qubit) -def _recognize_CRz(cmd): +def _recognize_CRz(cmd): # pylint: disable=invalid-name """Recognize the controlled Rz gate.""" return get_control_count(cmd) >= 1 diff --git a/projectq/setups/decompositions/entangle.py b/projectq/setups/decompositions/entangle.py index 59943ab1d..918be886e 100755 --- a/projectq/setups/decompositions/entangle.py +++ b/projectq/setups/decompositions/entangle.py @@ -26,13 +26,13 @@ def _decompose_entangle(cmd): """Decompose the entangle gate.""" - qr = cmd.qubits[0] + qureg = cmd.qubits[0] eng = cmd.engine with Control(eng, cmd.control_qubits): - H | qr[0] - with Control(eng, qr[0]): - All(X) | qr[1:] + H | qureg[0] + with Control(eng, qureg[0]): + All(X) | qureg[1:] #: Decomposition rules diff --git a/projectq/setups/decompositions/globalphase.py b/projectq/setups/decompositions/globalphase.py index 3b546a361..69e38f081 100755 --- a/projectq/setups/decompositions/globalphase.py +++ b/projectq/setups/decompositions/globalphase.py @@ -23,12 +23,11 @@ from projectq.ops import Ph -def _decompose_PhNoCtrl(cmd): +def _decompose_PhNoCtrl(cmd): # pylint: disable=invalid-name,unused-argument """Throw out global phases (no controls).""" - pass -def _recognize_PhNoCtrl(cmd): +def _recognize_PhNoCtrl(cmd): # pylint: disable=invalid-name """Recognize global phases (no controls).""" return get_control_count(cmd) == 0 diff --git a/projectq/setups/decompositions/h2rx.py b/projectq/setups/decompositions/h2rx.py index 68b7f6866..c9f27092a 100644 --- a/projectq/setups/decompositions/h2rx.py +++ b/projectq/setups/decompositions/h2rx.py @@ -27,7 +27,7 @@ from projectq.ops import Ph, Rx, Ry, H -def _decompose_h2rx_M(cmd): +def _decompose_h2rx_M(cmd): # pylint: disable=invalid-name """Decompose the Ry gate.""" # Labelled 'M' for 'minus' because decomposition ends with a Ry(-pi/2) qubit = cmd.qubits[0] @@ -36,7 +36,7 @@ def _decompose_h2rx_M(cmd): Ry(-1 * math.pi / 2) | qubit -def _decompose_h2rx_N(cmd): +def _decompose_h2rx_N(cmd): # pylint: disable=invalid-name """Decompose the Ry gate.""" # Labelled 'N' for 'neutral' because decomposition doesn't end with # Ry(pi/2) or Ry(-pi/2) @@ -46,7 +46,7 @@ def _decompose_h2rx_N(cmd): Rx(-1 * math.pi) | qubit -def _recognize_HNoCtrl(cmd): +def _recognize_HNoCtrl(cmd): # pylint: disable=invalid-name """For efficiency reasons only if no control qubits.""" return get_control_count(cmd) == 0 diff --git a/projectq/setups/decompositions/ph2r.py b/projectq/setups/decompositions/ph2r.py index 4f14e2c26..c72f459ee 100755 --- a/projectq/setups/decompositions/ph2r.py +++ b/projectq/setups/decompositions/ph2r.py @@ -24,7 +24,7 @@ from projectq.ops import Ph, R -def _decompose_Ph(cmd): +def _decompose_Ph(cmd): # pylint: disable=invalid-name """Decompose the controlled phase gate (C^nPh(phase)).""" ctrl = cmd.control_qubits gate = cmd.gate @@ -34,7 +34,7 @@ def _decompose_Ph(cmd): R(gate.angle) | ctrl[0] -def _recognize_Ph(cmd): +def _recognize_Ph(cmd): # pylint: disable=invalid-name """Recognize the controlled phase gate.""" return get_control_count(cmd) >= 1 diff --git a/projectq/setups/decompositions/phaseestimation.py b/projectq/setups/decompositions/phaseestimation.py index 1d7fc3f32..5059ad1ec 100644 --- a/projectq/setups/decompositions/phaseestimation.py +++ b/projectq/setups/decompositions/phaseestimation.py @@ -91,7 +91,7 @@ def two_qubit_gate(system_q, time): from projectq.ops import QPE -def _decompose_QPE(cmd): +def _decompose_QPE(cmd): # pylint: disable=invalid-name """Decompose the Quantum Phase Estimation gate.""" eng = cmd.engine @@ -103,20 +103,20 @@ def _decompose_QPE(cmd): Tensor(H) | qpe_ancillas # The Unitary Operator - U = cmd.gate.unitary + unitary = cmd.gate.unitary # Control U on the system_qubits - if callable(U): + if callable(unitary): # If U is a function - for i in range(len(qpe_ancillas)): - with Control(eng, qpe_ancillas[i]): - U(system_qubits, time=2 ** i) + for i, ancilla in enumerate(qpe_ancillas): + with Control(eng, ancilla): + unitary(system_qubits, time=2 ** i) else: - for i in range(len(qpe_ancillas)): + for i, ancilla in enumerate(qpe_ancillas): ipower = int(2 ** i) with Loop(eng, ipower): - with Control(eng, qpe_ancillas[i]): - U | system_qubits + with Control(eng, ancilla): + unitary | system_qubits # Inverse QFT on the ancillas get_inverse(QFT) | qpe_ancillas diff --git a/projectq/setups/decompositions/phaseestimation_test.py b/projectq/setups/decompositions/phaseestimation_test.py index 43aa5ec16..118641d7e 100644 --- a/projectq/setups/decompositions/phaseestimation_test.py +++ b/projectq/setups/decompositions/phaseestimation_test.py @@ -17,6 +17,7 @@ import cmath import numpy as np +from flaky import flaky import pytest from projectq.backends import Simulator @@ -34,6 +35,7 @@ import projectq.setups.decompositions.uniformlycontrolledr2cnot as ucr2cnot +@flaky(max_runs=5, min_passes=2) def test_simple_test_X_eigenvectors(): rule_set = DecompositionRuleSet(modules=[pe, dqft]) eng = MainEngine( @@ -43,7 +45,7 @@ def test_simple_test_X_eigenvectors(): ], ) results = np.array([]) - for i in range(100): + for i in range(150): autovector = eng.allocate_qureg(1) X | autovector H | autovector @@ -66,6 +68,7 @@ def test_simple_test_X_eigenvectors(): ) +@flaky(max_runs=5, min_passes=2) def test_Ph_eigenvectors(): rule_set = DecompositionRuleSet(modules=[pe, dqft]) eng = MainEngine( @@ -75,7 +78,7 @@ def test_Ph_eigenvectors(): ], ) results = np.array([]) - for i in range(100): + for i in range(150): autovector = eng.allocate_qureg(1) theta = cmath.pi * 2.0 * 0.125 unit = Ph(theta) @@ -103,6 +106,7 @@ def two_qubit_gate(system_q, time): CNOT | (system_q[0], system_q[1]) +@flaky(max_runs=5, min_passes=2) def test_2qubitsPh_andfunction_eigenvectors(): rule_set = DecompositionRuleSet(modules=[pe, dqft]) eng = MainEngine( @@ -112,7 +116,7 @@ def test_2qubitsPh_andfunction_eigenvectors(): ], ) results = np.array([]) - for i in range(100): + for i in range(150): autovector = eng.allocate_qureg(2) X | autovector[0] ancillas = eng.allocate_qureg(3) diff --git a/projectq/setups/decompositions/qft2crandhadamard.py b/projectq/setups/decompositions/qft2crandhadamard.py index 5119e19ee..157f8e98a 100755 --- a/projectq/setups/decompositions/qft2crandhadamard.py +++ b/projectq/setups/decompositions/qft2crandhadamard.py @@ -29,7 +29,7 @@ from projectq.meta import Control -def _decompose_QFT(cmd): +def _decompose_QFT(cmd): # pylint: disable=invalid-name qb = cmd.qubits[0] eng = cmd.engine with Control(eng, cmd.control_qubits): diff --git a/projectq/setups/decompositions/qubitop2onequbit.py b/projectq/setups/decompositions/qubitop2onequbit.py index 4e24effad..c4cc7873b 100644 --- a/projectq/setups/decompositions/qubitop2onequbit.py +++ b/projectq/setups/decompositions/qubitop2onequbit.py @@ -29,7 +29,8 @@ def _recognize_qubitop(cmd): def _decompose_qubitop(cmd): - assert len(cmd.qubits) == 1 + if len(cmd.qubits) != 1: + raise ValueError('QubitOperator decomposition can only accept a single quantum register') qureg = cmd.qubits[0] eng = cmd.engine qubit_op = cmd.gate diff --git a/projectq/setups/decompositions/qubitop2onequbit_test.py b/projectq/setups/decompositions/qubitop2onequbit_test.py index ddf542ffa..91d95b4d3 100644 --- a/projectq/setups/decompositions/qubitop2onequbit_test.py +++ b/projectq/setups/decompositions/qubitop2onequbit_test.py @@ -26,7 +26,8 @@ InstructionFilter, ) from projectq.meta import Control -from projectq.ops import All, Measure, Ph, QubitOperator, X, Y, Z +from projectq.ops import All, Measure, Ph, QubitOperator, X, Y, Z, Command +from projectq.types import WeakQubitRef import projectq.setups.decompositions.qubitop2onequbit as qubitop2onequbit @@ -54,6 +55,13 @@ def _decomp_gates(eng, cmd): return True +def test_qubitop2singlequbit_invalid(): + qb0 = WeakQubitRef(None, idx=0) + qb1 = WeakQubitRef(None, idx=1) + with pytest.raises(ValueError): + qubitop2onequbit._decompose_qubitop(Command(None, QubitOperator(), ([qb0], [qb1]))) + + def test_qubitop2singlequbit(): num_qubits = 4 random_initial_state = [0.2 + 0.1 * x * cmath.exp(0.1j + 0.2j * x) for x in range(2 ** (num_qubits + 1))] diff --git a/projectq/setups/decompositions/r2rzandph.py b/projectq/setups/decompositions/r2rzandph.py index 2e7a94e22..dbd204721 100755 --- a/projectq/setups/decompositions/r2rzandph.py +++ b/projectq/setups/decompositions/r2rzandph.py @@ -24,7 +24,7 @@ from projectq.ops import Ph, Rz, R -def _decompose_R(cmd): +def _decompose_R(cmd): # pylint: disable=invalid-name """Decompose the (controlled) phase-shift gate, denoted by R(phase).""" ctrl = cmd.control_qubits eng = cmd.engine diff --git a/projectq/setups/decompositions/rx2rz.py b/projectq/setups/decompositions/rx2rz.py index 41f5a3c7f..eb64f63bf 100644 --- a/projectq/setups/decompositions/rx2rz.py +++ b/projectq/setups/decompositions/rx2rz.py @@ -34,7 +34,7 @@ def _decompose_rx(cmd): Uncompute(eng) -def _recognize_RxNoCtrl(cmd): +def _recognize_RxNoCtrl(cmd): # pylint: disable=invalid-name """For efficiency reasons only if no control qubits.""" return get_control_count(cmd) == 0 diff --git a/projectq/setups/decompositions/ry2rz.py b/projectq/setups/decompositions/ry2rz.py index aa2a0bd65..4dc3dca1c 100644 --- a/projectq/setups/decompositions/ry2rz.py +++ b/projectq/setups/decompositions/ry2rz.py @@ -36,7 +36,7 @@ def _decompose_ry(cmd): Uncompute(eng) -def _recognize_RyNoCtrl(cmd): +def _recognize_RyNoCtrl(cmd): # pylint: disable=invalid-name """For efficiency reasons only if no control qubits.""" return get_control_count(cmd) == 0 diff --git a/projectq/setups/decompositions/rz2rx.py b/projectq/setups/decompositions/rz2rx.py index afa96b436..380b14a19 100644 --- a/projectq/setups/decompositions/rz2rx.py +++ b/projectq/setups/decompositions/rz2rx.py @@ -28,7 +28,7 @@ from projectq.ops import Rx, Ry, Rz -def _decompose_rz2rx_P(cmd): +def _decompose_rz2rx_P(cmd): # pylint: disable=invalid-name """Decompose the Rz using negative angle.""" # Labelled 'P' for 'plus' because decomposition ends with a Ry(+pi/2) qubit = cmd.qubits[0] @@ -42,7 +42,7 @@ def _decompose_rz2rx_P(cmd): Uncompute(eng) -def _decompose_rz2rx_M(cmd): +def _decompose_rz2rx_M(cmd): # pylint: disable=invalid-name """Decompose the Rz using positive angle.""" # Labelled 'M' for 'minus' because decomposition ends with a Ry(-pi/2) qubit = cmd.qubits[0] @@ -56,7 +56,7 @@ def _decompose_rz2rx_M(cmd): Uncompute(eng) -def _recognize_RzNoCtrl(cmd): +def _recognize_RzNoCtrl(cmd): # pylint: disable=invalid-name """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 diff --git a/projectq/setups/decompositions/sqrtswap2cnot.py b/projectq/setups/decompositions/sqrtswap2cnot.py index f2441c2b0..4c9ce919a 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot.py +++ b/projectq/setups/decompositions/sqrtswap2cnot.py @@ -23,7 +23,11 @@ def _decompose_sqrtswap(cmd): """Decompose (controlled) swap gates.""" - assert len(cmd.qubits) == 2 and len(cmd.qubits[0]) == 1 and len(cmd.qubits[1]) == 1 + + if len(cmd.qubits) != 2: + raise ValueError('SqrtSwap gate requires two quantum registers') + if not (len(cmd.qubits[0]) == 1 and len(cmd.qubits[1]) == 1): + raise ValueError('SqrtSwap gate requires must act on only 2 qubits') ctrl = cmd.control_qubits qubit0 = cmd.qubits[0][0] qubit1 = cmd.qubits[1][0] diff --git a/projectq/setups/decompositions/sqrtswap2cnot_test.py b/projectq/setups/decompositions/sqrtswap2cnot_test.py index 02dd3362d..ace87a94e 100644 --- a/projectq/setups/decompositions/sqrtswap2cnot_test.py +++ b/projectq/setups/decompositions/sqrtswap2cnot_test.py @@ -24,8 +24,8 @@ DummyEngine, InstructionFilter, ) - -from projectq.ops import All, Measure, SqrtSwap +from projectq.ops import All, Measure, SqrtSwap, Command +from projectq.types import WeakQubitRef import projectq.setups.decompositions.sqrtswap2cnot as sqrtswap2cnot @@ -36,6 +36,18 @@ def _decomp_gates(eng, cmd): return True +def test_sqrtswap_invalid(): + qb0 = WeakQubitRef(engine=None, idx=0) + qb1 = WeakQubitRef(engine=None, idx=1) + qb2 = WeakQubitRef(engine=None, idx=2) + + with pytest.raises(ValueError): + sqrtswap2cnot._decompose_sqrtswap(Command(None, SqrtSwap, ([qb0], [qb1], [qb2]))) + + with pytest.raises(ValueError): + sqrtswap2cnot._decompose_sqrtswap(Command(None, SqrtSwap, ([qb0], [qb1, qb2]))) + + def test_sqrtswap(): for basis_state in ([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]): correct_dummy_eng = DummyEngine(save_commands=True) diff --git a/projectq/setups/decompositions/stateprep2cnot.py b/projectq/setups/decompositions/stateprep2cnot.py index 6073bcc14..c82bd62b9 100644 --- a/projectq/setups/decompositions/stateprep2cnot.py +++ b/projectq/setups/decompositions/stateprep2cnot.py @@ -29,12 +29,13 @@ ) -def _decompose_state_preparation(cmd): +def _decompose_state_preparation(cmd): # pylint: disable=too-many-locals """ Implements state preparation based on arXiv:quant-ph/0407010v1. """ eng = cmd.engine - assert len(cmd.qubits) == 1 + if len(cmd.qubits) != 1: + raise ValueError('StatePreparation does not support multiple quantum registers!') num_qubits = len(cmd.qubits[0]) qureg = cmd.qubits[0] final_state = cmd.gate.final_state @@ -52,17 +53,17 @@ def _decompose_state_preparation(cmd): phase_of_blocks = [] for amplitude in final_state: phase_of_blocks.append(cmath.phase(amplitude)) - for target_qubit in range(len(qureg)): + for qubit_idx, qubit in enumerate(qureg): angles = [] phase_of_next_blocks = [] - for block in range(2 ** (len(qureg) - target_qubit - 1)): + for block in range(2 ** (len(qureg) - qubit_idx - 1)): phase0 = phase_of_blocks[2 * block] phase1 = phase_of_blocks[2 * block + 1] angles.append(phase0 - phase1) phase_of_next_blocks.append((phase0 + phase1) / 2.0) UniformlyControlledRz(angles) | ( - qureg[(target_qubit + 1) :], # noqa: E203 - qureg[target_qubit], + qureg[(qubit_idx + 1) :], # noqa: E203 + qubit, ) phase_of_blocks = phase_of_next_blocks # Cancel global phase @@ -71,20 +72,20 @@ def _decompose_state_preparation(cmd): abs_of_blocks = [] for amplitude in final_state: abs_of_blocks.append(abs(amplitude)) - for target_qubit in range(len(qureg)): + for qubit_idx, qubit in enumerate(qureg): angles = [] abs_of_next_blocks = [] - for block in range(2 ** (len(qureg) - target_qubit - 1)): - a0 = abs_of_blocks[2 * block] - a1 = abs_of_blocks[2 * block + 1] + for block in range(2 ** (len(qureg) - qubit_idx - 1)): + a0 = abs_of_blocks[2 * block] # pylint: disable=invalid-name + a1 = abs_of_blocks[2 * block + 1] # pylint: disable=invalid-name if a0 == 0 and a1 == 0: angles.append(0) else: angles.append(-2.0 * math.acos(a0 / math.sqrt(a0 ** 2 + a1 ** 2))) abs_of_next_blocks.append(math.sqrt(a0 ** 2 + a1 ** 2)) UniformlyControlledRy(angles) | ( - qureg[(target_qubit + 1) :], # noqa: E203 - qureg[target_qubit], + qureg[(qubit_idx + 1) :], # noqa: E203 + qubit, ) abs_of_blocks = abs_of_next_blocks diff --git a/projectq/setups/decompositions/stateprep2cnot_test.py b/projectq/setups/decompositions/stateprep2cnot_test.py index 381414ac0..ffa510ce1 100644 --- a/projectq/setups/decompositions/stateprep2cnot_test.py +++ b/projectq/setups/decompositions/stateprep2cnot_test.py @@ -29,6 +29,14 @@ import projectq.setups.decompositions.stateprep2cnot as stateprep2cnot +def test_invalid_arguments(): + qb0 = WeakQubitRef(engine=None, idx=0) + qb1 = WeakQubitRef(engine=None, idx=1) + cmd = Command(None, StatePreparation([0, 1j]), qubits=([qb0], [qb1])) + with pytest.raises(ValueError): + stateprep2cnot._decompose_state_preparation(cmd) + + def test_wrong_final_state(): qb0 = WeakQubitRef(engine=None, idx=0) qb1 = WeakQubitRef(engine=None, idx=1) diff --git a/projectq/setups/decompositions/time_evolution.py b/projectq/setups/decompositions/time_evolution.py index 6ca4789ce..1bce70fd5 100644 --- a/projectq/setups/decompositions/time_evolution.py +++ b/projectq/setups/decompositions/time_evolution.py @@ -33,15 +33,14 @@ def _recognize_time_evolution_commuting_terms(cmd): hamiltonian = cmd.gate.hamiltonian if len(hamiltonian.terms) == 1: return False - else: - id_op = QubitOperator((), 0.0) - for term in hamiltonian.terms: - test_op = QubitOperator(term, hamiltonian.terms[term]) - for other in hamiltonian.terms: - other_op = QubitOperator(other, hamiltonian.terms[other]) - commutator = test_op * other_op - other_op * test_op - if not commutator.isclose(id_op, rel_tol=1e-9, abs_tol=1e-9): - return False + id_op = QubitOperator((), 0.0) + for term in hamiltonian.terms: + test_op = QubitOperator(term, hamiltonian.terms[term]) + for other in hamiltonian.terms: + other_op = QubitOperator(other, hamiltonian.terms[other]) + commutator = test_op * other_op - other_op * test_op + if not commutator.isclose(id_op, rel_tol=1e-9, abs_tol=1e-9): + return False return True @@ -60,7 +59,7 @@ def _recognize_time_evolution_individual_terms(cmd): return len(cmd.gate.hamiltonian.terms) == 1 -def _decompose_time_evolution_individual_terms(cmd): +def _decompose_time_evolution_individual_terms(cmd): # pylint: disable=too-many-branches """ Implements a TimeEvolution gate with a hamiltonian having only one term. @@ -78,19 +77,22 @@ def _decompose_time_evolution_individual_terms(cmd): Nielsen and Chuang, Quantum Computation and Information. """ - assert len(cmd.qubits) == 1 + if len(cmd.qubits) != 1: + raise ValueError('TimeEvolution gate can only accept a single quantum register') qureg = cmd.qubits[0] eng = cmd.engine time = cmd.gate.time hamiltonian = cmd.gate.hamiltonian - assert len(hamiltonian.terms) == 1 + if len(hamiltonian.terms) != 1: + raise ValueError('This decomposition function only accepts single-term hamiltonians!') term = list(hamiltonian.terms)[0] coefficient = hamiltonian.terms[term] check_indices = set() # Check that hamiltonian is not identity term, # Previous __or__ operator should have apply a global phase instead: - assert not term == () + if term == (): + raise ValueError('This decomposition function cannot accept a hamiltonian with an empty term!') # hamiltonian has only a single local operator if len(term) == 1: @@ -112,8 +114,10 @@ def _decompose_time_evolution_individual_terms(cmd): H | qureg[index] elif action == 'Y': Rx(math.pi / 2.0) | qureg[index] + print(check_indices, set(range(len(qureg)))) # Check that qureg had exactly as many qubits as indices: - assert check_indices == set((range(len(qureg)))) + if check_indices != set(range(len(qureg))): + raise ValueError('Indices mismatch between hamiltonian terms and qubits') # Compute parity for i in range(len(qureg) - 1): CNOT | (qureg[i], qureg[i + 1]) diff --git a/projectq/setups/decompositions/time_evolution_test.py b/projectq/setups/decompositions/time_evolution_test.py index 79397e07f..293aba089 100644 --- a/projectq/setups/decompositions/time_evolution_test.py +++ b/projectq/setups/decompositions/time_evolution_test.py @@ -31,16 +31,8 @@ DecompositionRuleSet, ) from projectq.meta import Control -from projectq.ops import ( - QubitOperator, - TimeEvolution, - ClassicalInstructionGate, - Ph, - Rx, - Ry, - All, - Measure, -) +from projectq.ops import QubitOperator, TimeEvolution, ClassicalInstructionGate, Ph, Rx, Ry, All, Measure, Command +from projectq.types import WeakQubitRef from . import time_evolution as te @@ -145,6 +137,28 @@ def test_recognize_individual_terms(): assert te.rule_individual_terms.gate_recognizer(cmd3) +def test_decompose_individual_terms_invalid(): + eng = MainEngine(backend=DummyEngine(), engine_list=[]) + qb0 = WeakQubitRef(eng, idx=0) + qb1 = WeakQubitRef(eng, idx=1) + op1 = QubitOperator("X0 Y1", 0.5) + op2 = op1 + QubitOperator("Y2 X4", -0.5) + op3 = QubitOperator(tuple(), 0.5) + op4 = QubitOperator("X0 Y0", 0.5) + + with pytest.raises(ValueError): + te._decompose_time_evolution_individual_terms(Command(eng, TimeEvolution(1, op1), ([qb0], [qb1]))) + + with pytest.raises(ValueError): + te._decompose_time_evolution_individual_terms(Command(eng, TimeEvolution(1, op2), ([qb0],))) + + with pytest.raises(ValueError): + te._decompose_time_evolution_individual_terms(Command(eng, TimeEvolution(1, op3), ([qb0],))) + + with pytest.raises(ValueError): + te._decompose_time_evolution_individual_terms(Command(eng, TimeEvolution(1, op4), ([qb0, qb1],))) + + def test_decompose_individual_terms(): saving_eng = DummyEngine(save_commands=True) diff --git a/projectq/setups/decompositions/toffoli2cnotandtgate.py b/projectq/setups/decompositions/toffoli2cnotandtgate.py index 94b42bcab..c3e794d75 100755 --- a/projectq/setups/decompositions/toffoli2cnotandtgate.py +++ b/projectq/setups/decompositions/toffoli2cnotandtgate.py @@ -28,23 +28,21 @@ def _decompose_toffoli(cmd): ctrl = cmd.control_qubits target = cmd.qubits[0] - c1 = ctrl[0] - c2 = ctrl[1] H | target - CNOT | (c1, target) - T | c1 + CNOT | (ctrl[0], target) + T | ctrl[0] Tdag | target - CNOT | (c2, target) - CNOT | (c2, c1) - Tdag | c1 + CNOT | (ctrl[1], target) + CNOT | (ctrl[1], ctrl[0]) + Tdag | ctrl[0] T | target - CNOT | (c2, c1) - CNOT | (c1, target) + CNOT | (ctrl[1], ctrl[0]) + CNOT | (ctrl[0], target) Tdag | target - CNOT | (c2, target) + CNOT | (ctrl[1], target) T | target - T | c2 + T | ctrl[1] H | target diff --git a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py index a1ce53d85..a04263b18 100644 --- a/projectq/setups/decompositions/uniformlycontrolledr2cnot.py +++ b/projectq/setups/decompositions/uniformlycontrolledr2cnot.py @@ -21,7 +21,9 @@ from projectq.ops import CNOT, Ry, Rz, UniformlyControlledRy, UniformlyControlledRz -def _apply_ucr_n(angles, ucontrol_qubits, target_qubit, eng, gate_class, rightmost_cnot): +def _apply_ucr_n( + angles, ucontrol_qubits, target_qubit, eng, gate_class, rightmost_cnot +): # pylint: disable=too-many-arguments if len(ucontrol_qubits) == 0: gate = gate_class(angles[0]) if gate != gate_class(0): diff --git a/projectq/setups/default.py b/projectq/setups/default.py index b31d98fcf..942b66894 100755 --- a/projectq/setups/default.py +++ b/projectq/setups/default.py @@ -26,5 +26,8 @@ def get_engine_list(): + """ + Return the default list of compiler engine. + """ rule_set = DecompositionRuleSet(modules=[projectq.setups.decompositions]) return [TagRemover(), LocalOptimizer(10), AutoReplacer(rule_set), TagRemover(), LocalOptimizer(10)] diff --git a/projectq/setups/grid.py b/projectq/setups/grid.py index 9204bd7ae..49dd393fb 100644 --- a/projectq/setups/grid.py +++ b/projectq/setups/grid.py @@ -15,59 +15,17 @@ """ Defines a setup to compile to qubits placed in 2-D grid. -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 only two qubit gates and arbitrary single -qubit gates. ProjectQ's GridMapper is then used to introduce the -necessary Swap operations to route interacting qubits next to each other. -This setup allows to choose the final gate set (with some limitations). +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 only two qubit gates and arbitrary single qubit +gates. ProjectQ's GridMapper is then used to introduce the necessary Swap operations to route interacting qubits next +to each other. This setup allows to choose the final gate set (with some limitations). """ -import inspect -import projectq -import projectq.libs.math -import projectq.setups.decompositions -from projectq.cengines import ( - AutoReplacer, - DecompositionRuleSet, - InstructionFilter, - GridMapper, - LocalOptimizer, - TagRemover, -) -from projectq.ops import ( - BasicMathGate, - ClassicalInstructionGate, - CNOT, - ControlledGate, - get_inverse, - QFT, - Swap, -) +from projectq.cengines import GridMapper +from projectq.ops import CNOT, Swap - -def high_level_gates(eng, cmd): - """ - Remove any MathGates. - """ - g = cmd.gate - if g == QFT or get_inverse(g) == QFT or g == Swap: - return True - elif isinstance(g, BasicMathGate): - return False - return True - - -def one_and_two_qubit_gates(eng, cmd): - all_qubits = [q for qr in cmd.all_qubits for q in qr] - if isinstance(cmd.gate, ClassicalInstructionGate): - # This is required to allow Measure, Allocate, Deallocate, Flush - return True - elif len(all_qubits) <= 2: - return True - else: - return False +from ._utils import get_engine_list_linear_grid_base def get_engine_list(num_rows, num_columns, one_qubit_gates="any", two_qubit_gates=(CNOT, Swap)): @@ -75,18 +33,14 @@ def get_engine_list(num_rows, num_columns, one_qubit_gates="any", two_qubit_gate Returns an engine list to compile to a 2-D grid of qubits. Note: - If you choose a new gate set for which the compiler does not yet have - standard rules, it raises an `NoGateDecompositionError` or a - `RuntimeError: maximum recursion depth exceeded...`. Also note that - even the gate sets which work might not yet be optimized. So make sure - to double check and potentially extend the decomposition rules. - This implemention currently requires that the one qubit gates must - contain Rz and at least one of {Ry(best), Rx, H} and the two qubit gate - must contain CNOT (recommended) or CZ. + If you choose a new gate set for which the compiler does not yet have standard rules, it raises an + `NoGateDecompositionError` or a `RuntimeError: maximum recursion depth exceeded...`. Also note that even the + gate sets which work might not yet be optimized. So make sure to double check and potentially extend the + decomposition rules. This implemention currently requires that the one qubit gates must contain Rz and at + least one of {Ry(best), Rx, H} and the two qubit gate must contain CNOT (recommended) or CZ. 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. Example: get_engine_list(num_rows=2, num_columns=3, @@ -96,86 +50,18 @@ def get_engine_list(num_rows, num_columns, one_qubit_gates="any", two_qubit_gate Args: num_rows(int): Number of rows in the grid num_columns(int): Number of columns in the grid. - one_qubit_gates: "any" allows any one qubit gate, otherwise provide - a tuple of the allowed gates. If the gates are - instances of a class (e.g. X), it allows all gates - which are equal to it. If the gate is a class (Rz), it - allows all instances of this class. Default is "any" - two_qubit_gates: "any" allows any two qubit gate, otherwise provide - a tuple of the allowed gates. If the gates are - instances of a class (e.g. CNOT), it allows all gates - which are equal to it. If the gate is a class, it - allows all instances of this class. - Default is (CNOT, Swap). + one_qubit_gates: "any" allows any one qubit gate, otherwise provide a tuple of the allowed gates. If the gates + are instances of a class (e.g. X), it allows all gates which are equal to it. If the gate is + a class (Rz), it allows all instances of this class. Default is "any" + two_qubit_gates: "any" allows any two qubit gate, otherwise provide a tuple of the allowed gates. If the gates + are instances of a class (e.g. CNOT), it allows all gates which are equal to it. If the gate + is a class, it allows all instances of this class. Default is (CNOT, Swap). Raises: TypeError: If input is for the gates is not "any" or a tuple. Returns: A list of suitable compiler engines. """ - if two_qubit_gates != "any" and not isinstance(two_qubit_gates, tuple): - raise TypeError( - "two_qubit_gates parameter must be 'any' or a tuple. " - "When supplying only one gate, make sure to correctly " - "create the tuple (don't miss the comma), " - "e.g. two_qubit_gates=(CNOT,)" - ) - if one_qubit_gates != "any" and not isinstance(one_qubit_gates, tuple): - raise TypeError("one_qubit_gates parameter must be 'any' or a tuple.") - - rule_set = DecompositionRuleSet(modules=[projectq.libs.math, projectq.setups.decompositions]) - allowed_gate_classes = [] - allowed_gate_instances = [] - if one_qubit_gates != "any": - for gate in one_qubit_gates: - if inspect.isclass(gate): - allowed_gate_classes.append(gate) - else: - allowed_gate_instances.append((gate, 0)) - if two_qubit_gates != "any": - for gate in two_qubit_gates: - if inspect.isclass(gate): - # Controlled gate classes don't yet exists and would require - # separate treatment - assert not isinstance(gate, ControlledGate) - allowed_gate_classes.append(gate) - else: - if isinstance(gate, ControlledGate): - allowed_gate_instances.append((gate._gate, gate._n)) - else: - allowed_gate_instances.append((gate, 0)) - allowed_gate_classes = tuple(allowed_gate_classes) - allowed_gate_instances = tuple(allowed_gate_instances) - - def low_level_gates(eng, cmd): - all_qubits = [q for qr in cmd.all_qubits for q in qr] - assert len(all_qubits) <= 2 - if isinstance(cmd.gate, ClassicalInstructionGate): - # This is required to allow Measure, Allocate, Deallocate, Flush - return True - elif one_qubit_gates == "any" and len(all_qubits) == 1: - return True - elif two_qubit_gates == "any" and len(all_qubits) == 2: - return True - elif isinstance(cmd.gate, allowed_gate_classes): - return True - elif (cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances: - return True - else: - return False - - return [ - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(high_level_gates), - LocalOptimizer(5), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(one_and_two_qubit_gates), - LocalOptimizer(5), - GridMapper(num_rows=num_rows, num_columns=num_columns), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(low_level_gates), - LocalOptimizer(5), - ] + return get_engine_list_linear_grid_base( + GridMapper(num_rows=num_rows, num_columns=num_columns), one_qubit_gates, two_qubit_gates + ) diff --git a/projectq/setups/ibm.py b/projectq/setups/ibm.py index e1db71a9f..559f8efdc 100755 --- a/projectq/setups/ibm.py +++ b/projectq/setups/ibm.py @@ -36,6 +36,9 @@ def get_engine_list(token=None, device=None): + """ + Return the default list of compiler engine for the IBM QE platform + """ # Access to the hardware properties via show_devices # Can also be extended to take into account gate fidelities, new available # gate, etc.. @@ -110,15 +113,18 @@ def get_engine_list(token=None, device=None): class DeviceOfflineError(Exception): - pass + """Exception raised if a selected device is currently offline""" class DeviceNotHandledError(Exception): - pass + """Exception raised if a selected device is cannot handle the circuit""" def list2set(coupling_list): + """ + Convert a list() to a set() + """ result = [] - for el in coupling_list: - result.append(tuple(el)) + for element in coupling_list: + result.append(tuple(element)) return set(result) diff --git a/projectq/setups/ionq.py b/projectq/setups/ionq.py index c68251eb9..985faa19d 100644 --- a/projectq/setups/ionq.py +++ b/projectq/setups/ionq.py @@ -44,6 +44,9 @@ def get_engine_list(token=None, device=None): + """ + Return the default list of compiler engine for the IonQ platform + """ devices = show_devices(token) if not device or device not in devices: raise DeviceOfflineError("Error checking engine list: no '{}' devices available".format(device)) diff --git a/projectq/setups/linear.py b/projectq/setups/linear.py index 2f8c80e02..b0ff5a7e8 100644 --- a/projectq/setups/linear.py +++ b/projectq/setups/linear.py @@ -15,59 +15,16 @@ """ Defines a setup to compile to qubits placed in a linear chain or a circle. -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 only two qubit gates and arbitrary single -qubit gates. ProjectQ's LinearMapper is then used to introduce the necessary -Swap operations to route interacting qubits next to each other. This setup -allows to choose the final gate set (with some limitations). +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 only two qubit gates and arbitrary single qubit +gates. ProjectQ's LinearMapper is then used to introduce the necessary Swap operations to route interacting qubits +next to each other. This setup allows to choose the final gate set (with some limitations). """ -import inspect +from projectq.cengines import LinearMapper +from projectq.ops import CNOT, Swap -import projectq -import projectq.libs.math -import projectq.setups.decompositions -from projectq.cengines import ( - AutoReplacer, - DecompositionRuleSet, - InstructionFilter, - LinearMapper, - LocalOptimizer, - TagRemover, -) -from projectq.ops import ( - BasicMathGate, - ClassicalInstructionGate, - CNOT, - ControlledGate, - get_inverse, - QFT, - Swap, -) - - -def high_level_gates(eng, cmd): - """ - Remove any MathGates. - """ - g = cmd.gate - if g == QFT or get_inverse(g) == QFT or g == Swap: - return True - elif isinstance(g, BasicMathGate): - return False - return True - - -def one_and_two_qubit_gates(eng, cmd): - all_qubits = [q for qr in cmd.all_qubits for q in qr] - if isinstance(cmd.gate, ClassicalInstructionGate): - # This is required to allow Measure, Allocate, Deallocate, Flush - return True - elif len(all_qubits) <= 2: - return True - else: - return False +from ._utils import get_engine_list_linear_grid_base def get_engine_list(num_qubits, cyclic=False, one_qubit_gates="any", two_qubit_gates=(CNOT, Swap)): @@ -75,18 +32,14 @@ def get_engine_list(num_qubits, cyclic=False, one_qubit_gates="any", two_qubit_g Returns an engine list to compile to a linear chain of qubits. Note: - If you choose a new gate set for which the compiler does not yet have - standard rules, it raises an `NoGateDecompositionError` or a - `RuntimeError: maximum recursion depth exceeded...`. Also note that - even the gate sets which work might not yet be optimized. So make sure - to double check and potentially extend the decomposition rules. - This implemention currently requires that the one qubit gates must - contain Rz and at least one of {Ry(best), Rx, H} and the two qubit gate - must contain CNOT (recommended) or CZ. + If you choose a new gate set for which the compiler does not yet have standard rules, it raises an + `NoGateDecompositionError` or a `RuntimeError: maximum recursion depth exceeded...`. Also note that even the + gate sets which work might not yet be optimized. So make sure to double check and potentially extend the + decomposition rules. This implemention currently requires that the one qubit gates must contain Rz and at + least one of {Ry(best), Rx, H} and the two qubit gate must contain CNOT (recommended) or CZ. 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. Example: get_engine_list(num_qubits=10, cyclic=False, @@ -96,86 +49,18 @@ def get_engine_list(num_qubits, cyclic=False, one_qubit_gates="any", two_qubit_g Args: num_qubits(int): Number of qubits in the chain cyclic(bool): If a circle or not. Default is False - one_qubit_gates: "any" allows any one qubit gate, otherwise provide - a tuple of the allowed gates. If the gates are - instances of a class (e.g. X), it allows all gates - which are equal to it. If the gate is a class (Rz), it - allows all instances of this class. Default is "any" - two_qubit_gates: "any" allows any two qubit gate, otherwise provide - a tuple of the allowed gates. If the gates are - instances of a class (e.g. CNOT), it allows all gates - which are equal to it. If the gate is a class, it - allows all instances of this class. - Default is (CNOT, Swap). + one_qubit_gates: "any" allows any one qubit gate, otherwise provide a tuple of the allowed gates. If the gates + are instances of a class (e.g. X), it allows all gates which are equal to it. If the gate is + a class (Rz), it allows all instances of this class. Default is "any" + two_qubit_gates: "any" allows any two qubit gate, otherwise provide a tuple of the allowed gates. If the gates + are instances of a class (e.g. CNOT), it allows all gates which are equal to it. If the gate + is a class, it allows all instances of this class. Default is (CNOT, Swap). Raises: TypeError: If input is for the gates is not "any" or a tuple. Returns: A list of suitable compiler engines. """ - if two_qubit_gates != "any" and not isinstance(two_qubit_gates, tuple): - raise TypeError( - "two_qubit_gates parameter must be 'any' or a tuple. " - "When supplying only one gate, make sure to correctly " - "create the tuple (don't miss the comma), " - "e.g. two_qubit_gates=(CNOT,)" - ) - if one_qubit_gates != "any" and not isinstance(one_qubit_gates, tuple): - raise TypeError("one_qubit_gates parameter must be 'any' or a tuple.") - - rule_set = DecompositionRuleSet(modules=[projectq.libs.math, projectq.setups.decompositions]) - allowed_gate_classes = [] - allowed_gate_instances = [] - if one_qubit_gates != "any": - for gate in one_qubit_gates: - if inspect.isclass(gate): - allowed_gate_classes.append(gate) - else: - allowed_gate_instances.append((gate, 0)) - if two_qubit_gates != "any": - for gate in two_qubit_gates: - if inspect.isclass(gate): - # Controlled gate classes don't yet exists and would require - # separate treatment - assert not isinstance(gate, ControlledGate) - allowed_gate_classes.append(gate) - else: - if isinstance(gate, ControlledGate): - allowed_gate_instances.append((gate._gate, gate._n)) - else: - allowed_gate_instances.append((gate, 0)) - allowed_gate_classes = tuple(allowed_gate_classes) - allowed_gate_instances = tuple(allowed_gate_instances) - - def low_level_gates(eng, cmd): - all_qubits = [q for qr in cmd.all_qubits for q in qr] - assert len(all_qubits) <= 2 - if isinstance(cmd.gate, ClassicalInstructionGate): - # This is required to allow Measure, Allocate, Deallocate, Flush - return True - elif one_qubit_gates == "any" and len(all_qubits) == 1: - return True - elif two_qubit_gates == "any" and len(all_qubits) == 2: - return True - elif isinstance(cmd.gate, allowed_gate_classes): - return True - elif (cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances: - return True - else: - return False - - return [ - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(high_level_gates), - LocalOptimizer(5), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(one_and_two_qubit_gates), - LocalOptimizer(5), - LinearMapper(num_qubits=num_qubits, cyclic=cyclic), - AutoReplacer(rule_set), - TagRemover(), - InstructionFilter(low_level_gates), - LocalOptimizer(5), - ] + return get_engine_list_linear_grid_base( + LinearMapper(num_qubits=num_qubits, cyclic=cyclic), one_qubit_gates, two_qubit_gates + ) diff --git a/projectq/setups/restrictedgateset.py b/projectq/setups/restrictedgateset.py index f12877bcb..c6d7f034f 100644 --- a/projectq/setups/restrictedgateset.py +++ b/projectq/setups/restrictedgateset.py @@ -35,48 +35,22 @@ ) from projectq.ops import ( BasicGate, - BasicMathGate, ClassicalInstructionGate, CNOT, ControlledGate, - get_inverse, - QFT, - Swap, ) +from ._utils import one_and_two_qubit_gates, high_level_gates -def high_level_gates(eng, cmd): + +def default_chooser(cmd, decomposition_list): # pylint: disable=unused-argument """ - Remove any MathGates. + Default chooser function for the AutoReplacer compiler engine. """ - g = cmd.gate - if eng.next_engine.is_available(cmd): - return True - elif g == QFT or get_inverse(g) == QFT or g == Swap: - return True - elif isinstance(g, BasicMathGate): - return False - return True - - -def one_and_two_qubit_gates(eng, cmd): - all_qubits = [q for qr in cmd.all_qubits for q in qr] - if isinstance(cmd.gate, ClassicalInstructionGate): - # This is required to allow Measure, Allocate, Deallocate, Flush - return True - elif eng.next_engine.is_available(cmd): - return True - elif len(all_qubits) <= 2: - return True - else: - return False - - -def default_chooser(cmd, decomposition_list): return decomposition_list[0] -def get_engine_list( +def get_engine_list( # pylint: disable=too-many-branches,too-many-statements one_qubit_gates="any", two_qubit_gates=(CNOT,), other_gates=(), @@ -164,11 +138,12 @@ def get_engine_list( if inspect.isclass(gate): # Controlled gate classes don't yet exists and would require # separate treatment - assert not isinstance(gate, ControlledGate) + if isinstance(gate, ControlledGate): # pragma: no cover + raise RuntimeError('Support for controlled gate not implemented!') allowed_gate_classes2.append(gate) elif isinstance(gate, BasicGate): if isinstance(gate, ControlledGate): - allowed_gate_instances2.append((gate._gate, gate._n)) + allowed_gate_instances2.append((gate._gate, gate._n)) # pylint: disable=protected-access else: allowed_gate_instances2.append((gate, 0)) else: @@ -177,11 +152,12 @@ def get_engine_list( if inspect.isclass(gate): # Controlled gate classes don't yet exists and would require # separate treatment - assert not isinstance(gate, ControlledGate) + if isinstance(gate, ControlledGate): # pragma: no cover + raise RuntimeError('Support for controlled gate not implemented!') allowed_gate_classes.append(gate) elif isinstance(gate, BasicGate): if isinstance(gate, ControlledGate): - allowed_gate_instances.append((gate._gate, gate._n)) + allowed_gate_instances.append((gate._gate, gate._n)) # pylint: disable=protected-access else: allowed_gate_instances.append((gate, 0)) else: @@ -193,26 +169,26 @@ def get_engine_list( allowed_gate_classes2 = tuple(allowed_gate_classes2) allowed_gate_instances2 = tuple(allowed_gate_instances2) - def low_level_gates(eng, cmd): + def low_level_gates(eng, cmd): # pylint: disable=unused-argument,too-many-return-statements all_qubits = [q for qr in cmd.all_qubits for q in qr] if isinstance(cmd.gate, ClassicalInstructionGate): # This is required to allow Measure, Allocate, Deallocate, Flush return True - elif one_qubit_gates == "any" and len(all_qubits) == 1: + if one_qubit_gates == "any" and len(all_qubits) == 1: return True - elif two_qubit_gates == "any" and len(all_qubits) == 2: + if two_qubit_gates == "any" and len(all_qubits) == 2: return True - elif isinstance(cmd.gate, allowed_gate_classes): + if isinstance(cmd.gate, allowed_gate_classes): return True - elif (cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances: + if (cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances: return True - elif isinstance(cmd.gate, allowed_gate_classes1) and len(all_qubits) == 1: + if isinstance(cmd.gate, allowed_gate_classes1) and len(all_qubits) == 1: return True - elif isinstance(cmd.gate, allowed_gate_classes2) and len(all_qubits) == 2: + if isinstance(cmd.gate, allowed_gate_classes2) and len(all_qubits) == 2: return True - elif cmd.gate in allowed_gate_instances1 and len(all_qubits) == 1: + if cmd.gate in allowed_gate_instances1 and len(all_qubits) == 1: return True - elif (cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances2 and len(all_qubits) == 2: + if (cmd.gate, len(cmd.control_qubits)) in allowed_gate_instances2 and len(all_qubits) == 2: return True return False diff --git a/projectq/setups/trapped_ion_decomposer.py b/projectq/setups/trapped_ion_decomposer.py index 158073f93..4472b7c65 100644 --- a/projectq/setups/trapped_ion_decomposer.py +++ b/projectq/setups/trapped_ion_decomposer.py @@ -19,23 +19,21 @@ """ 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. +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. +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. +Because the decomposition chooser is only called when a gate has to be decomposed, this reduction will work better +when the entire circuit has to be decomposed. Otherwise, If the circuit has both superconding gates and native ion +trapped gates the decomposed circuit will not be optimal. """ from projectq.setups import restrictedgateset from projectq.ops import Rxx, Rx, Ry -from projectq.meta import get_control_count # ------------------chooser_Ry_reducer-------------------# # If the qubit is not in the prev_Ry_sign dictionary, then no decomposition @@ -51,7 +49,7 @@ # +1 -def chooser_Ry_reducer(cmd, decomposition_list): +def chooser_Ry_reducer(cmd, decomposition_list): # pylint: disable=invalid-name, too-many-return-statements """ Choose the decomposition so as to maximise Ry cancellations, based on the previous decomposition used for the given qubit. @@ -79,10 +77,9 @@ def chooser_Ry_reducer(cmd, decomposition_list): except IndexError: pass - local_prev_Ry_sign = prev_Ry_sign.setdefault(cmd.engine, dict()) + local_prev_Ry_sign = prev_Ry_sign.setdefault(cmd.engine, dict()) # pylint: disable=invalid-name if name == 'cnot2rxx': - assert get_control_count(cmd) == 1 ctrl_id = cmd.control_qubits[0].id if local_prev_Ry_sign.get(ctrl_id, -1) <= 0: @@ -100,7 +97,6 @@ def chooser_Ry_reducer(cmd, decomposition_list): if name == 'h2rx': qubit_id = [qb.id for qureg in cmd.qubits for qb in qureg] - assert len(qubit_id) == 1 # this should be a single qubit gate qubit_id = qubit_id[0] if local_prev_Ry_sign.get(qubit_id, 0) == 0: @@ -112,7 +108,6 @@ def chooser_Ry_reducer(cmd, decomposition_list): if name == 'rz2rx': qubit_id = [qb.id for qureg in cmd.qubits for qb in qureg] - assert len(qubit_id) == 1 # this should be a single qubit gate qubit_id = qubit_id[0] if local_prev_Ry_sign.get(qubit_id, -1) <= 0: diff --git a/projectq/types/__init__.py b/projectq/types/__init__.py index 96f7ecf24..d81996907 100755 --- a/projectq/types/__init__.py +++ b/projectq/types/__init__.py @@ -13,4 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ProjectQ module containing all basic types""" + from ._qubit import BasicQubit, Qubit, Qureg, WeakQubitRef diff --git a/projectq/types/_qubit.py b/projectq/types/_qubit.py index 96ffc2fe9..207efd08d 100755 --- a/projectq/types/_qubit.py +++ b/projectq/types/_qubit.py @@ -16,9 +16,8 @@ This file defines BasicQubit, Qubit, WeakQubit and Qureg. A Qureg represents a list of Qubit or WeakQubit objects. -Qubit represents a (logical-level) qubit with a unique index provided by the -MainEngine. Qubit objects are automatically deallocated if they go out of -scope and intented to be used within Qureg objects in user code. +A Qubit represents a (logical-level) qubit with a unique index provided by the MainEngine. Qubit objects are +automatically deallocated if they go out of scope and intented to be used within Qureg objects in user code. Example: .. code-block:: python @@ -27,15 +26,13 @@ eng = MainEngine() qubit = eng.allocate_qubit() -qubit is a Qureg of size 1 with one Qubit object which is deallocated once -qubit goes out of scope. +qubit is a Qureg of size 1 with one Qubit object which is deallocated once qubit goes out of scope. -WeakQubit are used inside the Command object and are not automatically -deallocated. +WeakQubit are used inside the Command object and are not automatically deallocated. """ -class BasicQubit(object): +class BasicQubit: """ BasicQubit objects represent qubits. @@ -61,21 +58,13 @@ def __str__(self): def __bool__(self): """ - Access the result of a previous measurement and return False / True - (0 / 1) + Access the result of a previous measurement and return False / True (0 / 1) """ return self.engine.main_engine.get_measurement_result(self) - def __nonzero__(self): - """ - Access the result of a previous measurement for Python 2.7. - """ - return self.__bool__() - def __int__(self): """ - Access the result of a previous measurement and return as integer - (0 / 1). + Access the result of a previous measurement and return as integer (0 / 1). """ return int(bool(self)) @@ -97,8 +86,7 @@ def __hash__(self): """ Return the hash of this qubit. - Hash definition because of custom __eq__. - Enables storing a qubit in, e.g., a set. + Hash definition because of custom __eq__. Enables storing a qubit in, e.g., a set. """ if self.id == -1: return object.__hash__(self) @@ -109,13 +97,10 @@ class Qubit(BasicQubit): """ Qubit class. - Represents a (logical-level) qubit with a unique index provided by the - MainEngine. Once the qubit goes out of scope (and is garbage-collected), - it deallocates itself automatically, allowing automatic resource - management. + Represents a (logical-level) qubit with a unique index provided by the MainEngine. Once the qubit goes out of scope + (and is garbage-collected), it deallocates itself automatically, allowing automatic resource management. - Thus the qubit is not copyable; only returns a reference to the same - object. + Thus the qubit is not copyable; only returns a reference to the same object. """ def __del__(self): @@ -124,10 +109,9 @@ def __del__(self): """ if self.id == -1: return - # If a user directly calls this function, then the qubit gets id == -1 - # but stays in active_qubits as it is not yet deleted, hence remove - # it manually (if the garbage collector calls this function, then the - # WeakRef in active qubits is already gone): + # If a user directly calls this function, then the qubit gets id == -1 but stays in active_qubits as it is not + # yet deleted, hence remove it manually (if the garbage collector calls this function, then the WeakRef in + # active qubits is already gone): if self in self.engine.main_engine.active_qubits: self.engine.main_engine.active_qubits.remove(self) weak_copy = WeakQubitRef(self.engine, self.id) @@ -139,8 +123,7 @@ def __copy__(self): Non-copyable (returns reference to self). Note: - To prevent problems with automatic deallocation, qubits are not - copyable! + To prevent problems with automatic deallocation, qubits are not copyable! """ return self @@ -149,33 +132,28 @@ def __deepcopy__(self, memo): Non-deepcopyable (returns reference to self). Note: - To prevent problems with automatic deallocation, qubits are not - deepcopyable! + To prevent problems with automatic deallocation, qubits are not deepcopyable! """ return self -class WeakQubitRef(BasicQubit): +class WeakQubitRef(BasicQubit): # pylint: disable=too-few-public-methods """ WeakQubitRef objects are used inside the Command object. - Qubits feature automatic deallocation when destroyed. WeakQubitRefs, on - the other hand, do not share this feature, allowing to copy them and pass - them along the compiler pipeline, while the actual qubit objects may be - garbage-collected (and, thus, cleaned up early). Otherwise there is no - difference between a WeakQubitRef and a Qubit object. + Qubits feature automatic deallocation when destroyed. WeakQubitRefs, on the other hand, do not share this feature, + allowing to copy them and pass them along the compiler pipeline, while the actual qubit objects may be + garbage-collected (and, thus, cleaned up early). Otherwise there is no difference between a WeakQubitRef and a Qubit + object. """ - pass - class Qureg(list): """ Quantum register class. - Simplifies accessing measured values for single-qubit registers (no []- - access necessary) and enables pretty-printing of general quantum registers - (call Qureg.__str__(qureg)). + Simplifies accessing measured values for single-qubit registers (no []- access necessary) and enables + pretty-printing of general quantum registers (call Qureg.__str__(qureg)). """ def __bool__(self): @@ -183,40 +161,28 @@ def __bool__(self): Return measured value if Qureg consists of 1 qubit only. Raises: - Exception if more than 1 qubit resides in this register (then you - need to specify which value to get using qureg[???]) + Exception if more than 1 qubit resides in this register (then you need to specify which value to get using + qureg[???]) """ if len(self) == 1: return bool(self[0]) - else: - raise Exception( - "__bool__(qureg): Quantum register contains more than 1 qubit. Use __bool__(qureg[idx]) instead." - ) + raise Exception( + "__bool__(qureg): Quantum register contains more than 1 qubit. Use __bool__(qureg[idx]) instead." + ) def __int__(self): """ Return measured value if Qureg consists of 1 qubit only. Raises: - Exception if more than 1 qubit resides in this register (then you - need to specify which value to get using qureg[???]) + Exception if more than 1 qubit resides in this register (then you need to specify which value to get using + qureg[???]) """ if len(self) == 1: return int(self[0]) - else: - raise Exception( - "__int__(qureg): Quantum register contains more than 1 qubit. Use __bool__(qureg[idx]) instead." - ) - - def __nonzero__(self): - """ - Return measured value if Qureg consists of 1 qubit only for Python 2.7. - - Raises: - Exception if more than 1 qubit resides in this register (then you - need to specify which value to get using qureg[???]) - """ - return int(self) != 0 + raise Exception( + "__int__(qureg): Quantum register contains more than 1 qubit. Use __bool__(qureg[idx]) instead." + ) def __str__(self): """ diff --git a/projectq/types/_qubit_test.py b/projectq/types/_qubit_test.py index dfcad755c..54287749c 100755 --- a/projectq/types/_qubit_test.py +++ b/projectq/types/_qubit_test.py @@ -45,9 +45,7 @@ def test_basic_qubit_measurement(): assert int(qubit1) == 1 # Testing functions for python 2 and python 3 assert not qubit0.__bool__() - assert not qubit0.__nonzero__() assert qubit1.__bool__() - assert qubit1.__nonzero__() @pytest.mark.parametrize("id0, id1, expected", [(0, 0, True), (0, 1, False)]) @@ -164,9 +162,7 @@ def test_qureg_measure_if_qubit(): assert int(qureg1) == 1 # Testing functions for python 2 and python 3 assert not qureg0.__bool__() - assert not qureg0.__nonzero__() assert qureg1.__bool__() - assert qureg1.__nonzero__() def test_qureg_measure_exception(): @@ -177,8 +173,6 @@ def test_qureg_measure_exception(): qureg.append(qubit) with pytest.raises(Exception): qureg.__bool__() - with pytest.raises(Exception): - qureg.__nonzero__() with pytest.raises(Exception): qureg.__int__() diff --git a/pyproject.toml b/pyproject.toml index e2d959ca0..9f58329ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,8 @@ build-backend = "setuptools.build_meta" '*.mo', '.clang-format', '.gitmodules', + 'requirements.txt', + 'requirements_tests.txt', 'VERSION.txt', '.editorconfig', '*.yml', @@ -49,22 +51,46 @@ build-backend = "setuptools.build_meta" [tool.pylint] - [tools.pylint.master] + [tool.pylint.master] ignore-patterns = [ - '__init__.py' + '__init__.py', + '.*_test.py', + '.*_fixtures.py', + '.*flycheck.*.py', + 'docs/.*', + 'examples/.*', ] - [tools.pylint.format] + extension-pkg-whitelist = [ + 'math', + 'cmath', + 'unicodedata', + 'revkit' + ] + extension-pkg-allow-list = [ + 'math', + 'cmath', + 'unicodedata', + 'revkit' + ] + + [tool.pylint.basic] + good-names = ['qb', 'id', 'i', 'j', 'k', 'N', 'op', 'X', 'Y', 'Z', 'R', 'C', 'CRz', 'Zero', 'One'] + + [tool.pylint.format] max-line-length = 120 - [tools.pylint.reports] + [tool.pylint.reports] msg-template = '{path}:{line}: [{msg_id}, {obj}] {msg} ({symbol})' - [tools.pylint.messages_control] + [tool.pylint.similarities] + min-similarity-lines = 20 + + [tool.pylint.messages_control] disable = [ - 'invalid-name', 'expression-not-assigned', - 'pointless-statemen', + 'pointless-statement', + 'fixme' ] @@ -73,6 +99,7 @@ build-backend = "setuptools.build_meta" minversion = '6.0' addopts = '-pno:warnings' testpaths = ['projectq'] +ignore-glob = ['*flycheck*.py'] mock_use_standalone_module = true [tool.setuptools_scm] diff --git a/requirements_tests.txt b/requirements_tests.txt deleted file mode 100644 index ea01acbbc..000000000 --- a/requirements_tests.txt +++ /dev/null @@ -1,5 +0,0 @@ -flaky -mock -pytest >= 6.0 -pytest-cov -pytest-mock diff --git a/setup.cfg b/setup.cfg index 3d0ebb9c7..f01474cb7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,6 +30,9 @@ classifier = zip_safe = False packages = find: python_requires = >= 3 +setup_requires = + setuptools_scm[toml] + pybind11 >= 2 install_requires = matplotlib >= 2.2.3 networkx >= 2 @@ -40,6 +43,20 @@ install_requires = [options.extras_require] braket = boto3 +revkit = + revkit == 3.0a2.dev2 + dormouse +test = + flaky + mock + pytest >= 6.0 + pytest-cov + pytest-mock + +docs = + sphinx + sphinx_rtd_theme + # ============================================================================== diff --git a/setup.py b/setup.py index 9be796a62..acb57eb05 100755 --- a/setup.py +++ b/setup.py @@ -36,8 +36,8 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. -from __future__ import print_function -from setuptools import setup, Extension +"""Setup.py file""" + import distutils.log from distutils.cmd import Command from distutils.spawn import find_executable, spawn @@ -48,34 +48,39 @@ DistutilsExecError, DistutilsPlatformError, ) -from setuptools import Distribution as _Distribution -from setuptools.command.build_ext import build_ext -import sys import os -import subprocess import platform +import subprocess +import sys +import tempfile + +from setuptools import setup, Extension +from setuptools import Distribution as _Distribution +from setuptools.command.build_ext import build_ext # ============================================================================== # Helper functions and classes -class get_pybind_include(object): - """Helper class to determine the pybind11 include path - - The purpose of this class is to postpone importing pybind11 - until it is actually installed, so that the ``get_include()`` - method can be invoked.""" +class Pybind11Include: # pylint: disable=too-few-public-methods + """ + Helper class to determine the pybind11 include path The purpose of this class is to postpone importing pybind11 + until it is actually installed, so that the ``get_include()`` method can be invoked. + """ def __init__(self, user=False): self.user = user def __str__(self): - import pybind11 + import pybind11 # pylint: disable=import-outside-toplevel return pybind11.get_include(self.user) def important_msgs(*msgs): + """ + Print an important message. + """ print('*' * 75) for msg in msgs: print(msg) @@ -83,22 +88,27 @@ def important_msgs(*msgs): def status_msgs(*msgs): + """ + Print a status message. + """ print('-' * 75) for msg in msgs: print('# INFO: ', msg) print('-' * 75) -def compiler_test(compiler, flagname=None, link=False, include='', body='', postargs=None): +def compiler_test( + compiler, flagname=None, link=False, include='', body='', postargs=None +): # pylint: disable=too-many-arguments """ Return a boolean indicating whether a flag name is supported on the specified compiler. """ - import tempfile - f = tempfile.NamedTemporaryFile('w', suffix='.cpp', delete=False) - f.write('{}\nint main (int argc, char **argv) {{ {} return 0; }}'.format(include, body)) - f.close() + fname = None + with tempfile.NamedTemporaryFile('w', suffix='.cpp', delete=False) as temp: + temp.write('{}\nint main (int argc, char **argv) {{ {} return 0; }}'.format(include, body)) + fname = temp.name ret = True if postargs is None: @@ -111,10 +121,10 @@ def compiler_test(compiler, flagname=None, link=False, include='', body='', post if compiler.compiler_type == 'msvc': olderr = os.dup(sys.stderr.fileno()) - err = open('err.txt', 'w') + err = open('err.txt', 'w') # pylint: disable=consider-using-with os.dup2(err.fileno(), sys.stderr.fileno()) - obj_file = compiler.compile([f.name], extra_postargs=postargs) + obj_file = compiler.compile([fname], extra_postargs=postargs) if not os.path.exists(obj_file[0]): raise RuntimeError('') if link: @@ -128,37 +138,39 @@ def compiler_test(compiler, flagname=None, link=False, include='', body='', post raise RuntimeError('') except (CompileError, LinkError, RuntimeError): ret = False - os.unlink(f.name) + os.unlink(fname) return ret def _fix_macosx_header_paths(*args): # Fix path to SDK headers if necessary - _MACOSX_XCODE_REF_PATH = '/Applications/Xcode.app/Contents/' + 'Developer/Platforms/MacOSX.platform/' + 'Developer' - _MACOSX_DEVTOOLS_REF_PATH = '/Library/Developer/CommandLineTools/' + _MACOSX_XCODE_REF_PATH = ( # pylint: disable=invalid-name + '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer' + ) + _MACOSX_DEVTOOLS_REF_PATH = '/Library/Developer/CommandLineTools/' # pylint: disable=invalid-name _has_xcode = os.path.exists(_MACOSX_XCODE_REF_PATH) _has_devtools = os.path.exists(_MACOSX_DEVTOOLS_REF_PATH) if not _has_xcode and not _has_devtools: important_msgs('ERROR: Must install either Xcode or CommandLineTools!') raise BuildFailed() - def _do_replace(idx, item): - if not _has_xcode and _MACOSX_XCODE_REF_PATH in item: - compiler_args[idx] = item.replace(_MACOSX_XCODE_REF_PATH, _MACOSX_DEVTOOLS_REF_PATH) - - if not _has_devtools and _MACOSX_DEVTOOLS_REF_PATH in item: - compiler_args[idx] = item.replace(_MACOSX_DEVTOOLS_REF_PATH, _MACOSX_XCODE_REF_PATH) - for compiler_args in args: for idx, item in enumerate(compiler_args): - _do_replace(idx, item) + if not _has_xcode and _MACOSX_XCODE_REF_PATH in item: + compiler_args[idx] = item.replace(_MACOSX_XCODE_REF_PATH, _MACOSX_DEVTOOLS_REF_PATH) + + if not _has_devtools and _MACOSX_DEVTOOLS_REF_PATH in item: + compiler_args[idx] = item.replace(_MACOSX_DEVTOOLS_REF_PATH, _MACOSX_XCODE_REF_PATH) # ------------------------------------------------------------------------------ class BuildFailed(Exception): + """Extension raised if the build fails for any reason""" + def __init__(self): + super().__init__() self.cause = sys.exc_info()[1] # work around py 2/3 different syntax @@ -181,8 +193,8 @@ def __init__(self): ['projectq/backends/_sim/_cppsim.cpp'], include_dirs=[ # Path to pybind11 headers - get_pybind_include(), - get_pybind_include(user=True), + Pybind11Include(), + Pybind11Include(user=True), ], language='c++', ), @@ -203,7 +215,7 @@ class BuildExt(build_ext): ( 'gen-compiledb', None, - 'Generate a compile_commands.json alongside the compilation ' 'implies (-n/--dry-run)', + 'Generate a compile_commands.json alongside the compilation implies (-n/--dry-run)', ), ] @@ -216,13 +228,13 @@ def initialize_options(self): def finalize_options(self): build_ext.finalize_options(self) if self.gen_compiledb: - self.dry_run = True + self.dry_run = True # pylint: disable=attribute-defined-outside-init def run(self): try: build_ext.run(self) - except DistutilsPlatformError: - raise BuildFailed() + except DistutilsPlatformError as err: + raise BuildFailed() from err def build_extensions(self): self._configure_compiler() @@ -244,26 +256,27 @@ def build_extensions(self): } ) - import json + import json # pylint: disable=import-outside-toplevel with open( os.path.join(os.path.dirname(os.path.abspath(__file__)), 'compile_commands.json'), 'w', - ) as fd: - json.dump(compile_commands, fd, sort_keys=True, indent=4) + ) as json_file: + json.dump(compile_commands, json_file, sort_keys=True, indent=4) try: build_ext.build_extensions(self) - except ext_errors: - raise BuildFailed() - except ValueError: + except ext_errors as err: + raise BuildFailed() from err + except ValueError as err: # this can happen on Windows 64 bit, see Python issue 7511 if "'path'" in str(sys.exc_info()[1]): # works with both py 2/3 - raise BuildFailed() + raise BuildFailed() from err raise def _get_compilation_commands(self, ext): - (macros, objects, extra_postargs, pp_opts, build,) = self.compiler._setup_compile( + # pylint: disable=protected-access + (_, objects, extra_postargs, pp_opts, build,) = self.compiler._setup_compile( outdir=self.build_temp, sources=ext.sources, macros=ext.define_macros, @@ -294,6 +307,8 @@ def _get_compilation_commands(self, ext): return commands def _configure_compiler(self): + # pylint: disable=attribute-defined-outside-init + # Force dry_run = False to allow for compiler feature testing dry_run_old = self.compiler.dry_run self.compiler.dry_run = False @@ -311,8 +326,8 @@ def _configure_compiler(self): if compiler_test(self.compiler, '-stdlib=libc++'): self.c_opts['unix'] += ['-stdlib=libc++'] - ct = self.compiler.compiler_type - self.opts = self.c_opts.get(ct, []) + compiler_type = self.compiler.compiler_type + self.opts = self.c_opts.get(compiler_type, []) self.link_opts = [] if not compiler_test(self.compiler): @@ -335,7 +350,7 @@ def _configure_compiler(self): status_msgs('Other compiler tests') self.compiler.define_macro('VERSION_INFO', '"{}"'.format(self.distribution.get_version())) - if ct == 'unix' and compiler_test(self.compiler, '-fvisibility=hidden'): + if compiler_type == 'unix' and compiler_test(self.compiler, '-fvisibility=hidden'): self.opts.append('-fvisibility=hidden') self.compiler.dry_run = dry_run_old @@ -503,10 +518,9 @@ def run(self): try: cmd_obj.run() self.distribution.have_run[command] = 1 - assert self.distribution.ext_modules - except BuildFailed: + except BuildFailed as err: distutils.log.error('build_ext --dry-run --gen-compiledb command failed!') - raise RuntimeError('build_ext --dry-run --gen-compiledb command failed!') + raise RuntimeError('build_ext --dry-run --gen-compiledb command failed!') from err command = ['clang-tidy'] if self.warning_as_errors: @@ -523,40 +537,52 @@ class GenerateRequirementFile(Command): """A custom command to list the dependencies of the current""" description = 'List the dependencies of the current package' - user_options = [('include-extras', None, 'Include "extras_require" into the list')] - boolean_options = ['include-extras'] + user_options = [ + ('include-all-extras', None, 'Include all "extras_require" into the list'), + ('include-extras=', None, 'Include some of extras_requires into the list (comma separated)'), + ] + + boolean_options = ['include-all-extras'] def initialize_options(self): self.include_extras = None + self.include_all_extras = None + self.extra_pkgs = [] def finalize_options(self): - pass + include_extras = self.include_extras.split(',') + + try: + for name, pkgs in self.distribution.extras_require.items(): + if self.include_all_extras or name in include_extras: + self.extra_pkgs.extend(pkgs) + + except TypeError: # Mostly for old setuptools (< 30.x) + for name, pkgs in self.distribution.command_options['options.extras_require'].items(): + if self.include_all_extras or name in include_extras: + self.extra_pkgs.extend(pkgs) def run(self): - with open('requirements.txt', 'w') as fd: + with open('requirements.txt', 'w') as req_file: try: for pkg in self.distribution.install_requires: - fd.write('{}\n'.format(pkg)) - if self.include_extras: - for name, pkgs in self.distribution.extras_require.items(): - for pkg in pkgs: - fd.write('{}\n'.format(pkg)) - + req_file.write('{}\n'.format(pkg)) except TypeError: # Mostly for old setuptools (< 30.x) for pkg in self.distribution.command_options['options']['install_requires']: - fd.write('{}\n'.format(pkg)) - if self.include_extras: - for name, pkgs in self.distribution.command_options['options.extras_require'].items(): - location, pkgs = pkgs - for pkg in pkgs.split(): - fd.write('{}\n'.format(pkg)) + req_file.write('{}\n'.format(pkg)) + req_file.write('\n') + for pkg in self.extra_pkgs: + req_file.write('{}\n'.format(pkg)) # ------------------------------------------------------------------------------ class Distribution(_Distribution): - def has_ext_modules(self): + """Distribution class""" + + def has_ext_modules(self): # pylint: disable=no-self-use + """Return whether this distribution has some external modules""" # We want to always claim that we have ext_modules. This will be fine # if we don't actually have them (such as on PyPy) because nothing # will get built, however we don't want to provide an overally broad @@ -570,6 +596,7 @@ def has_ext_modules(self): def run_setup(with_cext): + """Run the setup() function""" kwargs = {} if with_cext: kwargs['ext_modules'] = ext_modules